← Назад

Комментарии и предложения

Заметил пару нюансов по PlazaTrader: 1)


_plazaStreamManager = new PlazaStreamManager(_plazaConnectionPool, TRequestType.RT_COMBINED_DYNAMIC, 1000);

Почему рекваест тайп фиксированный? надо конкретно задавать, например у меня так было:

"FORTS_FUTAGGR20_REPL" - RT_REMOTE_ONLINE "FORTS_FUTTRADE_REPL" - RT_REMOTE_ONLINE "FORTS_FUTINFO_REPL" - RT_COMBINED_DYNAMIC "FORTS_FUTCOMMON_REPL" - RT_REMOTE_ONLINE "FORTS_POS_REPL" - RT_COMBINED_DYNAMIC "FORTS_PART_REPL" - RT_COMBINED_DYNAMIC "FORTS_VM_REPL" - RT_COMBINED_DYNAMIC Допустим если FORTS_FUTCOMMON_REPL задать в RT_COMBINED_DYNAMIC, то при реконнекте будет перекачиваться весь поток заново. +Revision тоже надо указывать в некоторых потоках.

  1. в PlazaStreamManager void Run() Не обрабатывает состояния типа DS_STATE_ERROR или DS_STATE_CLOSE, те в случае чего будет затык и поток будет висеть закрытым или с ошибкой. Я для себя брал реализацию с примера(могу выложить если что) PS У меня несколько сместились приоритеты - придется задержаться на квике, но постораюсь не пропадать.

Комментарии (60)

Вход или Создать аккаунт, Войдите или зарегистрируйтесь, чтобы оставить комментарий

aspirant
aspirant

skuvv: Почему рекваест тайп фиксированный? Посмотрю в документации, спасибо за то, что обратил внимание.

skuvv: 2) в PlazaStreamManager void Run() Не обрабатывает состояния типа DS_STATE_ERROR или DS_STATE_CLOSE, те в случае чего будет затык и поток будет висеть закрытым или с ошибкой. Я для себя брал реализацию с примера(могу выложить если что) Эти состояния обрабатываются методом PlazaStream.Poll(CP2ConnectionClass connection). См. строчку 174

item.Stream.Poll(item.Connection);

Я тоже брал реализацию с примера🙂. При тесте некоторые потоки открывались не с первого раза: затыков не было.

skuvv
skuvv Автор

aspirant: Я тоже брал реализацию с примера🙂. При тесте некоторые потоки открывались не с первого раза: затыков не было. Тогда все ок 🙂

skuvv
skuvv Автор

Вопрос по _PlazaTrader.StartListeners() Каким образом планируется добавлять потоки? Сейчас по общему типу PlazaMarketData. Не лучше ли явно задавать (StreamName,TableName,TRequestType) ? допустим все StreamName в списке - просто выбираем из enum списке, потом на основе выбронного StreamName, выбираем из Dictionary<enum StreamName,enum TableName> выбираем TableName подготавлием метаданные(default загатовку для соответсвующей TableName) и подписываем поток. StreamName и TableName подготовить не проблема и народу понятно по документации будет, что где. PS Средней руки юзер типа меня не осилит процесс подписки(то что идет внутри StartListeners()), слишком замудренно 😀

aspirant
aspirant

skuvv: Вопрос по _PlazaTrader.StartListeners()

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

aspirant
aspirant

aspirant: Сегодня появились мысли, как все это сделать попроще.

Только что залил коммит:

  1. Удалил всякий мусор (PlazaDataBuffer, PlazaColumnSet)
  2. Переименовал PlazaTable в PlazaSystemTable.
  3. Добавил класс PlazaTable для клиентской подписки к потокам. Внутри него есть массив PlazaRecord[], в который попадают данные из потоков.
  4. Добавил в PlazaTrader метод public void StartListeners(params PlazaTable[] tables) для подписки на любые таблицы. Вот код клиентской подписки к двум потокам:
var optSessionTable = new PlazaTable(PlazaColumns.OptionSessionContents);
var rtsIndexTable = new PlazaTable(PlazaColumns.RtsIndex);
_plazaTrader.StartListeners(rtsIndexTable, optSessionTable);

В обоих случаях запрашиваются все колонки (поля) таблиц потоков Плазы. Если нужны не все колонки, можно использовать другой конструктор PlazaTable, в котором есть параметр IEnumerable<PlazaColumn> columns. Данные отлавливаются в событии public event Action<PlazaRecord[]> DataReceived; класса PlazaTable

Теперь насчет метода public void StartListeners(PlazaMarketData marketData). Он нужен для перегонки данных потоков в стандартные объекты, например создания коллекций инструментов и т.д., и поэтому нам необходимо контроллировать, какие подписки создаются и с каким набором колонок (полей). Любые другие предложения приветствуются.

skuvv
skuvv Автор

С выбором Dictionary я ошибся... Тогда как вариант - единый enum список с названиями типа StreamName_TableName. Чтото Например FORTS_FUTTRADE_REPL_orders_log А уже на приеме парсить сроку, все стримы оканчиваются на REPL.

skuvv
skuvv Автор

Я это и имел ввиду👍 в блоке ```

		foreach (var fieldInfo in this.GetType().GetFields().Where(x => x.FieldType == typeof(PlazaColumn)))
		{
			if (!(fieldInfo.Name == "ReplId" || fieldInfo.Name == "ReplRev" || fieldInfo.Name == "ReplAct"))
				result.Add((PlazaColumn)fieldInfo.GetValue(this));
		}
В некоторых таблицах добавляются одинаковые поля, например таблица FuturesSessionContents, поле MinStep
aspirant
aspirant

skuvv: В некоторых таблицах добавляются одинаковые поля, например таблица FuturesSessionContents, поле MinStep

Потому что тот (не я 😀), кто расписывал класс PlazaFuturesSessionContentsColumns, забыл исправить название поля Плазы, когда копировал переменные:

this.StepPrice = new PlazaColumn(PlazaTableRegistry.FutSessionContents, "min_step", typeof(decimal), "d16.5", true);

Я только что залил исправление.

При тестировании нужно будет проверить каждый класс в папке Metadata (создать из них таблицы Плазы). Я, когда тестировал алгоритм подключения к потокам, тоже натыкался на пару схожих ошибок.

skuvv
skuvv Автор

aspirant:

skuvv: В некоторых таблицах добавляются одинаковые поля, например таблица FuturesSessionContents, поле MinStep

Потому что тот (не я 😀), кто расписывал класс PlazaFuturesSessionContentsColumns, забыл исправить название поля Плазы, когда копировал переменные:

this.StepPrice = new PlazaColumn(PlazaTableRegistry.FutSessionContents, "min_step", typeof(decimal), "d16.5", true);

> 
> Я только что залил исправление.
Не заметил ночью 😀 
> При тестировании нужно будет проверить каждый класс в папке Metadata (создать из них таблицы Плазы).  Я, когда тестировал алгоритм подключения к потокам, тоже натыкался на пару схожих ошибок.
Займусь этим

skuvv
skuvv Автор

потоки FuturesAggregation не работают(не получают данные после выхода в онлайн режим), в логах клиента error: есть подозрение на <mark>lifenumber</mark> метатаблицы

2011-03-18 15:28:05.305;p2mq-cli;;MQ library started. 2011-03-18 15:28:05.309;p2repl-cli;;Client replication library started 2011-03-18 15:28:05.321;p2mq-cli;;Socket connected;conn 0x911DC14;host 'localhost';port 4001 2011-03-18 15:28:05.323;p2mq-cli;;Connection 0x911DB30 connected; connID 1 2011-03-18 15:28:07.085;p2mq-cli;;Socket connected;conn 0x9139B64;host '127.0.0.1';port 4001 2011-03-18 15:28:07.087;p2mq-cli;;Connection 0x9139A80 connected; connID 2 2011-03-18 15:28:07.096;p2repl-cli;;Stream created;stream 0xA442FA8 2011-03-18 15:28:07.096;P2ReplStorage;;Revs were inited; stream 0x0; cnt 1 2011-03-18 15:28:07.096;P2ReplStorage;; tbl_idx 0; cur_rev 1 2011-03-18 15:28:07.097;P2ReplStorage;;Basic policy created;stream 0x0,policy 0xA440EB8 2011-03-18 15:28:07.116;p2repl-cli;;Stream opening started;stream 0xA442FA8,type 2,name 'FORTS_FUTAGGR5_REPL',server_addr 'FINTER_FORTS3.inter_futaggr5' 2011-03-18 15:28:07.135;p2repl-cli;;Stream opened;stream 0xA442FA8;channel 0x4CF4F0D1358 2011-03-18 15:28:07.135;P2ReplStorage;;Revs were cleared after lifenum change; stream 0xA442FA8; cnt 1 2011-03-18 15:28:07.135;P2ReplStorage;; tbl_idx 0; cur_rev 0 2011-03-18 15:28:07.140;p2repl-cli;;CLOSE message posted;stream 0xA442FA8 2011-03-18 15:28:07.142;p2repl-cli;error;p2err 40962=0xA002 - 'REPL:OBJECT_CHANGED__';Stream state was changed;stream 0xA442FA8 2011-03-18 15:28:07.142;p2repl-cli;error;p2err 40962=0xA002 - 'REPL:OBJECT_CHANGED__';Couldn't start scheme setting (auto);stream 0xA442FA8 2011-03-18 15:28:07.143;P2ReplStorage;;Basic policy deleted;stream 0xA442FA8,policy 0xA440EB8 2011-03-18 15:28:07.148;P2ReplStorage;;Revs were inited; stream 0x0; cnt 1 2011-03-18 15:28:07.148;P2ReplStorage;; tbl_idx 0; cur_rev 2 2011-03-18 15:28:07.148;P2ReplStorage;;Basic policy created;stream 0x0,policy 0xA448968 2011-03-18 15:28:07.169;p2repl-cli;;Stream opening started;stream 0xA442FA8,type 2,name 'FORTS_FUTAGGR5_REPL',server_addr 'FINTER_FORTS3.inter_futaggr5' 2011-03-18 15:28:07.189;p2repl-cli;;Stream opened;stream 0xA442FA8;channel 0x4D04F0D1358 2011-03-18 15:28:07.190;p2repl-cli;;Scheme setting started;stream 0xA442FA8 2011-03-18 15:28:07.209;p2repl-cli;;Scheme is set;stream 0xA442FA8;channel 0x4D04F0D1358 2011-03-18 15:28:07.209;p2repl-cli;;Statistics module for table 'orders_aggr' was successfully registered 2011-03-18 15:28:07.210;p2repl-cli;;Statistics was registered;stream name FORTS_FUTAGGR5_REPL 2011-03-18 15:28:07.213;p2repl-cli;;Data requested;stream 0xA442FA8;channel 0x4D04F0D1358;session 0 2011-03-18 15:28:07.232;p2repl-cli;;DATA message received;stream 0xA442FA8 2011-03-18 15:28:07.232;p2repl-cli;;Stream is on-line;stream 0xA442FA8

skuvv
skuvv Автор

Пофиксил _dataStream.Close(); не нужно в OnStreamLifeNumChanged

skuvv
skuvv Автор

Проверил и пофиксил все таблицы(метаданные) начинающиеся на Futures. Можно принимать.

skuvv
skuvv Автор

События OnNewDataFromFuturesSessionContents и OnNewDataFromOptionSessionContents иногда не заполняют полностью Security - только создается new Security по id. Возможно метод GetSecurity не thread safe

Mikhail Sukhov
Mikhail Sukhov

skuvv: События OnNewDataFromFuturesSessionContents и OnNewDataFromOptionSessionContents иногда не заполняют полностью Security - только создается new Security по id. Возможно метод GetSecurity не thread safe

Больше вероятность, что при инициализации происходит ошибка. А GetSecurity сначала добавляется Security, и только уже потом вызывается делегат на заполнение полей. Вот там видимо и падает. Подозреваю, что не все понял проводятся. Например:

sec.MaxPrice = record[metadata.LimitUp].To<double>();

может упасть, если неправильный тип данных (например, РТС прислала пустое значение). Посмотрите на ProcessDataError.

aspirant
aspirant

Mikhail Sukhov: может упасть, если неправильный тип данных (например, РТС прислала пустое значение) Если будет неправильный тип данных, внутри обработчиков OnNewDataFromFuturesSessionContents и OnNewDataFromOptionSessionContents произойдет исключение. Я сначала пытался конвертировать данные из потока в DateTime через extenstion-метод To<DateTime>(). У меня срабатывало исключение. Потому уже воспользовался любезно кем-то добавленным PlazaRecord.GetAsDateTime().

Еще: надо было мне конечно предупредить, что я еще не дописал методы StartExport и StopExport. Сейчас, когда начал заливать PlazaTrader, увидел обновления skuvv'a. Вроде все вместе смержил.

Вопросы: все стандартные потоки добавлены?
Предложенная skuvv'ом фильтрация подойдет? Я вроде видел, что у PlazaTrader есть свой стандартный интерфейс влкючения/выключения фильтрации (RegisterSecurity/UnregisterSecurity)?

Mikhail Sukhov
Mikhail Sukhov

aspirant: Предложенная skuvv'ом фильтрация подойдет? Я вроде видел, что у PlazaTrader есть свой стандартный интерфейс влкючения/выключения фильтрации (RegisterSecurity/UnregisterSecurity)?

Мне не нравиться:

  1. Делается тоже самое, что и через RegisterXXX, но требует изменение внешнего контракта.
  2. Я думал фильтр передается внутрь стрима. Делать фильтр в самой PlazaTrader как-то не очень.
  3. Операция Exists очень медленная (фактически там полный перебор). На потоке тиков и стаканов все помрет.
Mikhail Sukhov
Mikhail Sukhov

aspirant: Я сначала пытался конвертировать данные из потока в DateTime через extenstion-метод To(). У меня срабатывало исключение. Потому уже воспользовался любезно кем-то добавленным PlazaRecord.GetAsDateTime().

  1. Все методы по переводу в и из плазовский формат нужно переносить в специальный класс - PlazaFormatter.

  2. Работа с PlazaRecord требует рефакторинг (прямо таки кричит):

  3. ... Это кто так учил писать?🙂 Нужно заменить на Dictionary.

  4. PlazaColumnList.Names не нужно ни в коем случае. Мы не работает со строчками. Мы работает с колонками - PlazaColumn. Соответственно, ключом в _data должен быть не string а PlazaColumn.

  5. Индексаторы у PlazaRecord не нужны. Лучше сделать в нем метод вида T Get(PlazaColumn), который бы конвертировал значение и при возникновении ошибки, перехватывал бы ее и выбрасывал свою с описанием того, что за колонка, что пришло на вход. Так пользователю будет проще понять ошибку (да и нам). [*]Опять возвращаемся к метаданным. Не нужно передавать в PlazaListener PlazaColumns. PlazaColumns - это абстрактный базовый класс для всех колонок. Для ограничения колонок необходимым нам множеством полей нужно использовать PlazaTable.Columns. Еще раз. PlazaTable - это как раз те настройки, которые описываю наши схемы на клиенте. PlazaColumns - базовый вспомогательный класс. PlazaColumnRegistry - коллекция всех возможных колонок. PlazaTableRegistry - коллекция всех возможных таблиц.

skuvv
skuvv Автор

aspirant: Вопросы: все стандартные потоки добавлены? Предложенная skuvv'ом фильтрация подойдет? Я вроде видел, что у PlazaTrader есть свой стандартный интерфейс влкючения/выключения фильтрации (RegisterSecurity/UnregisterSecurity)? Это наброски, понятно что надо все напильником доделывать. К примеру я сравнил проект со своей старой программой plaza2, данные в текущий s# проект поступают медленнее на 6-10мс(время снимал с OnNewSecurityChanged к примеру). А если включить реал плазу и стаканы глубиной 50, проект может не взлететь вообще )

aspirant
aspirant

Mikhail Sukhov: Все методы по переводу в и из плазовский формат нужно переносить в специальный класс - PlazaFormatter.

Добавил для плазовской даты. Так подойдет:

sec.ExpiryDate = record[metadata.DateEndTrade].ToString().FromPlazaString<DateTime>();
// или сделать:
sec.ExpiryDate = record[metadata.DateEndTrade].FromPlazaType<DateTime>();

Mikhail Sukhov: ... Это кто так учил писать?🙂 Нужно заменить на Dictionary. PlazaColumnList.Names не нужно ни в коем случае. Мы не работает со строчками. Мы работает с колонками - PlazaColumn. Соответственно, ключом в _data должен быть не string а PlazaColumn.

Знаю, что кричит. Но когда ключом был string, нужно было использовать такого монстра, чтобы работал индексатор по номеру колонки. Dictionary отсортировал бы свои элементы по алфавиту (ключи ведь string).

Сейчас я переделал на Dictionary с ключом PlazaColumn. Крик ушел🙂

Mikhail Sukhov: Индексаторы у PlazaRecord не нужны. Лучше сделать в нем метод вида T Get<T>(PlazaColumn) Добавил. Посмотри, пожалуйста. Кстати, я все-таки оставил индексатор по PlazaColumn.

Mikhail Sukhov: Опять возвращаемся к метаданным... Я изменил класс PlazaListener. Посмотри, пожалуйста, нужны твои комментарии + желательно кусок псевдокода - твое видение, как пользователь создает таблицу с набором колонок для передачи ее в PlazaListener.

aspirant
aspirant

skuvv: К примеру я сравнил проект со своей старой программой plaza2, данные в текущий s# проект поступают медленнее на 6-10мс(время снимал с OnNewSecurityChanged к примеру).

Получается, ты делал свою реализацию PlazaTrader?

Mikhail Sukhov
Mikhail Sukhov

aspirant: Dictionary отсортировал бы свои элементы по алфавиту (ключи ведь string).

Ты путаешь с SortedDictionary. И то там не по алфавиту... Это хеш алгоритм. Там сортировка не нужна. Группировка значения идет по хэшу ключа. Соответственно, когда обращаемся по ключу, сначала берется его хэш (PlazaColumn.GetHashCode) и получаются все значения, соответствующие хэшу. Операция O(1). И далее, если у нас коллизия, перебором всех ключей идет сравнение (PlazaColumn.Equals) и находится первый попавшийся (ага, уникальность там не проверяется).

Итого, в лучшем варианте O(1). В худшем O(n). Но чтобы добиться последнее, надо еще постараться.🙂

aspirant
aspirant

Mikhail Sukhov:

aspirant: Dictionary отсортировал бы свои элементы по алфавиту (ключи ведь string).

Ты путаешь с SortedDictionary. И то там не по алфавиту... Это хеш алгоритм. Там сортировка не нужна. Группировка значения идет по хэшу ключа. Соответственно, когда обращаемся по ключу, сначала берется его хэш (PlazaColumn.GetHashCode) и получаются все значения, соответствующие хэшу. Операция O(1). И далее, если у нас коллизия, перебором всех ключей идет сравнение (PlazaColumn.Equals) и находится первый попавшийся (ага, уникальность там не проверяется).

Итого, в лучшем варианте O(1). В худшем O(n). Но чтобы добиться последнее, надо еще постараться.🙂

Да, насчет сортировки я был не прав.

Mikhail Sukhov
Mikhail Sukhov

aspirant: Я изменил класс PlazaListener. Посмотри, пожалуйста, нужны твои комментарии + желательно кусок псевдокода - твое видение, как пользователь создает таблицу с набором колонок для передачи ее в PlazaListener.

Посмотрел и доделал кое-что.

Рассмешило название PlazaPartColumns. Долго думал, что за "часть". Оказалось, это Партфэл, дарагой!

Что осталось до переделать:

  1. Сейчас PlazaListener создается в самом начале со всеми колонками. Как я уже писал, использовать надо только те колонки, что определены схемами PlazaTable.Columns.
  2. PlazaListener выглядит как лишний класс (возможно, что в него потом будет засунута еще какая-то логика, если да, то какая?). Что он делает сейчас. Берет данные из PlazaStream и передает их в PlazaTrader. Тоесть, такой бюрократически посредник, который бумажки из стола на стол несет.
  3. enum TableName лучше сделать статическим классом со строковыми константами. Я так понял, такие безобразные названия нужны лишь для того, чтобы ini файлы создавать? Если да, то по логике простой. Мы создаем текстовый файл, используем string. Вся логика в имени полей TableName - приведение в string... Так может тогда сразу сделать string?
  4. Давайте подумаем насчет фильтрации. Сейчас это выглядит как то не очень. Аспирант, ты же вроде как уже что-то делал по фильтрам?
  5. PlazaSchemaParser предлагаю упрятать в PlazaTableSerializer.
skuvv
skuvv Автор

aspirant:

skuvv: К примеру я сравнил проект со своей старой программой plaza2, данные в текущий s# проект поступают медленнее на 6-10мс(время снимал с OnNewSecurityChanged к примеру).

Получается, ты делал свою реализацию PlazaTrader? Нет у меня не интерфейс как в s#, а напрямую работа c потоками и событиями

Mikhail Sukhov
Mikhail Sukhov

skuvv:

aspirant:

skuvv: К примеру я сравнил проект со своей старой программой plaza2, данные в текущий s# проект поступают медленнее на 6-10мс(время снимал с OnNewSecurityChanged к примеру).

Получается, ты делал свою реализацию PlazaTrader? Нет у меня не интерфейс как в s#, а напрямую работа c потоками и событиями

Может выложить это ввиде отдельного теста для сравнения перфоманса? Насчет OnNewSecurityChanged - конечно. Если вместо поиска по словарю устраивать Exist Find и т.д., то я не удивлюсь и пол минутными задержками.🙂

aspirant
aspirant

Mikhail Sukhov: Сейчас PlazaListener создается в самом начале со всеми колонками. Как я уже писал, использовать надо только те колонки, что определены схемами PlazaTable.Columns. Как клиентский код создает таблицу с колонками и схемами? Твое видение с куском кода.

Mikhail Sukhov: enum TableName лучше сделать статическим классом со строковыми константами. Я так понял, такие безобразные названия нужны лишь для того, чтобы ini файлы создавать? Если да, то по логике простой. Мы создаем текстовый файл, используем string. Вся логика в имени полей TableName - приведение в string... Так может тогда сразу сделать string? Этот enum был служебным, для внутреннего пользования. Клиенту его можно не показывать. Зачем я его сделал? Когда нужно набить пару десятков классов, enum + intellisense здорово помогает. Потом подстраховка от случайное опечатки в string. Сам я его не набивал: вместо этого пропарсил папку со всеми схемами.

Вообще PlazaTable сейчас и есть то, что ты предлагаешь. За исключением полей метаданных все остальные методы закомментены (причем, делал это не я). Можно сделать вот так:

// вместо: public TableName SystemName { get; private set; }
public string SystemName { get{ _systemName.ToString(); }

Mikhail Sukhov: Давайте подумаем насчет фильтрации. Сейчас это выглядит как то не очень. Я вообще собирался это делать внутри PlazaListener, но пока думал, skuvv успел привинтить свой фильтр🙂. Что насчет методов RegisterSecurity/UnRegisterSecurity? С помощью них будем включать фильтрацию или они нужно для чего-то другого?

Mikhail Sukhov: PlazaSchemaParser предлагаю упрятать в PlazaTableSerializer Сделаю, когда устаканится модель метаданных. С тех пор как я написал PlazaSchemaParser, модель уже раза-три менялась, и я решил отложить доделку этого класса на самый конец.

skuvv
skuvv Автор

Mikhail Sukhov:

skuvv:

aspirant:

skuvv: К примеру я сравнил проект со своей старой программой plaza2, данные в текущий s# проект поступают медленнее на 6-10мс(время снимал с OnNewSecurityChanged к примеру).

Получается, ты делал свою реализацию PlazaTrader? Нет у меня не интерфейс как в s#, а напрямую работа c потоками и событиями

Может выложить это ввиде отдельного теста для сравнения перфоманса? Насчет OnNewSecurityChanged - конечно. Если вместо поиска по словарю устраивать Exist Find и т.д., то я не удивлюсь и пол минутными задержками.🙂


void m_conn4_StreamDataInserted(CP2DataStream stream, String tableName, CP2Record rec)
        {
            try
            {
                if (stream.StreamName == streamFutCommonID)
                {
                    int index = instruments.GetIndex(rec.GetValAsLong("isin_id"));
                    if(index>=0)
                    {
                        double best_sell = Convert.ToDouble(rec.GetValAsVariant("best_sell"));
                        double best_buy = Convert.ToDouble(rec.GetValAsVariant("best_buy"));
                        if (best_buy != 0 && best_sell != 0)
                        {
                            instruments._bestask[index] = best_sell;
                            instruments._bestask_vol[index] = rec.GetValAsLong("amount_sell");
                            instruments._bestbid[index] = best_buy;
                            instruments._bestbid_vol[index] = rec.GetValAsLong("amount_buy");
                            instruments._lasttrade[index] = Convert.ToDouble(rec.GetValAsVariant("price"));
                            instruments._lasttrade_vol[index] = rec.GetValAsLong("amount");
                            instruments._lasttrade_time[index] = Convert.ToDateTime(rec.GetValAsString("deal_time"));
                            instruments._bidask_time[index] = DateTime.Now;

                            curr_rev_futcommon = rec.GetValAsLong("replRev");

                            log_buffer.Add(t4.Name, rec.GetValAsString("replID") + ";" + rec.GetValAsString("replRev") + ";" + instruments._isin_id[index] + ";" + instruments._bestask[index] + ";" + instruments._bestask_vol[index] + ";" +
                                instruments._bestbid[index] + ";" + instruments._bestbid_vol[index] + ";" + instruments._lasttrade[index] + ";" + instruments._lasttrade_vol[index] + ";" + instruments._lasttrade_time[index].ToString(timefmt) + ";" + rec.GetValAsString("deal_count") + ";" + rec.GetValAsString("pos"));
                        }
                    }
                }
            }
            catch (Exception e)
            {
                 log_buffer.Add("error", "[" + t4.Name + "] " + e.ToString());
            }
        }

Event datainserted Все типы таблицы я получал в соответсвующих им win thread. Событий никаких нет 😀, заполняется статик массив instruments,а роботы на каждом прогоне его чекают. Ну и в конце запись в текстовики, через самописный логгер. ps фильтрация опять же массиву instruments

Mikhail Sukhov
Mikhail Sukhov

aspirant:

Mikhail Sukhov: Сейчас PlazaListener создается в самом начале со всеми колонками. Как я уже писал, использовать надо только те колонки, что определены схемами PlazaTable.Columns. Как клиентский код создает таблицу с колонками и схемами? Твое видение с куском кода.

Взял за основу отсюда:

PlazaTableRegistry.FutCommon.Columns.Add(PlazaColumnRegistry.FuturesCommon.CurKotir);

PlazaTableRegistry.FutAggr5.Columns.Add(PlazaColumnRegistry.FuturesAggregation5.Moment);

aspirant:

Mikhail Sukhov: enum TableName лучше сделать статическим классом со строковыми константами. Я так понял, такие безобразные названия нужны лишь для того, чтобы ini файлы создавать? Если да, то по логике простой. Мы создаем текстовый файл, используем string. Вся логика в имени полей TableName - приведение в string... Так может тогда сразу сделать string? Этот enum был служебным, для внутреннего пользования. Клиенту его можно не показывать. Зачем я его сделал? Когда нужно набить пару десятков классов, enum + intellisense здорово помогает. Потом подстраховка от случайное опечатки в string. Сам я его не набивал: вместо этого пропарсил папку со всеми схемами.

Ок, не поняли друг друга. Тогда ремарка и код. Если enum используется для задания строковых констант, то используется класс со строками:

static class TableName
{
  public const string BaseContractsParams = "base_contracts_params";
  ..
}

public static PlazaTable FutAggr50 = new PlazaTable(TableName.BaseContractsParams, ReplicationStream.FutAggr50, "Фьючерсы: стакан глубиной 50 котировок");

На этом примере сразу видно недостаток именования перечислений (не ReplicationStreams, а ReplicationStream). Сходу кажется, что идет работа с потоком. А на самом деле к потокам это имеет о посредственное значение.

aspirant: Вообще PlazaTable сейчас и есть то, что ты предлагаешь. За исключением полей метаданных все остальные методы закомментены (причем, делал это не я). Можно сделать вот так:

// вместо: public TableName SystemName { get; private set; } public string SystemName { get{ _systemName.ToString(); }


Не понял, что за рокировка.

> **[aspirant](@message(7053)):**
> > **[Mikhail Sukhov](@message(7048)):**
> > Давайте подумаем насчет фильтрации. Сейчас это выглядит как то не очень.
> Я вообще собирался это делать внутри PlazaListener, но пока думал, skuvv успел привинтить свой фильтр🙂.  Что насчет методов RegisterSecurity/UnRegisterSecurity?  С помощью них будем включать фильтрацию или они нужно для чего-то другого?

Да, именно включение фильтра. Во всех АПИ (и я думаю в Плазе тоже рано или поздно сделают), фильтрация идет на серверной стороне и этот фильтра изменяется через методы, аналогичные Register Unregister.
aspirant
aspirant

Mikhail Sukhov: ...

Вопросы:

  1. То есть мы исходим из того, что клиент подписывается на каждый поток только один раз? Напоминаю, у Плазы можно подписываться на один поток несколько раз. Клиенты не будут спрашивать? Когда проект будет зарелизен, изменить архитекутру будет сложно.
  2. Что делать с нашими стандартными потоками, которые нужны, чтобы мапить объекты. Мы договорились, что их скроем от клиента, чтобы гарантировать в них нужный набор колоннок. Придется все равно создавать какие-то отдельные таблицы.

Mikhail Sukhov: Если enum используется для задания строковых констант, то используется класс со строками:

static class TableName...


OK, сделаю после фильтрации.  Сейчас все работает, а это рефакторинг функционала не добавит, только эстетику.

> **[Mikhail Sukhov](@message(7064)):**
> Не понял, что за рокировка.
Теперь она уже не нужна.

> **[Mikhail Sukhov](@message(7064)):**
> На этом примере сразу видно недостаток именования перечислений (не ReplicationStreams, а ReplicationStream)

Не знаю, дело вкуса.  Я уже привык к единственному числу.  Вот, кстати, MSDN'овский [guideline](http://msdn.microsoft.com/en-us/library/4x252001(v=vs.71).aspx).
Mikhail Sukhov
Mikhail Sukhov

aspirant: Вопросы:

  1. То есть мы исходим из того, что клиент подписывается на каждый поток только один раз? Напоминаю, у Плазы можно подписываться на один поток несколько раз. Клиенты не будут спрашивать? Когда проект будет зарелизен, изменить архитекутру будет сложно.
  1. Что делать с нашими стандартными потоками, которые нужны, чтобы мапить объекты. Мы договорились, что их скроем от клиента, чтобы гарантировать в них нужный набор колоннок. Придется все равно создавать какие-то отдельные таблицы.

  2. Давай еще раз обсудим. С точки зрения практики - зачем это нужно?

  3. Хм, а почему бы просто не оставить как есть сейчас?

aspirant
aspirant

Mikhail Sukhov:

  1. Давай еще раз обсудим. С точки зрения практики - зачем это нужно?

Сегодня думал на эту тему. Пожалуй, действительно ты прав: нужно упростить систему. Тем более что фильтры будут носить глобальный характер, т.е. вызвал RegisterSecurity(Security security) для какой-то бумаги, и все потоки сразу стали фильтровать только по ней, добавил еще одну - все потоки фильтруются по этим двум бумагам. Я правильно понял? Вопрос: события, связанные с приходом данных (DataInserted, DataDeleted и т.д.), перекидываю из PlazaListener в PlazaTable? Или всё в PlazaTrader, и добавляю в события первым параметром PlazaTable, чтобы можно было различать, из какой таблицы пришли данные? Больше нравится первое.

Mikhail Sukhov: 2. Хм, а почему бы просто не оставить как есть сейчас? Сейчас и для клиентского кода и для стандартных потоков единый механизм - подписка через PlazaListener. Если этот класс уйдет, нужно будет делать набор дополнительный набор стандартных таблиц, чтобы пользователь их не видел и не мог поменять набор колоннок.

Mikhail Sukhov
Mikhail Sukhov

aspirant: Сейчас и для клиентского кода и для стандартных потоков единый механизм - подписка через PlazaListener. Если этот класс уйдет, нужно будет делать набор дополнительный набор стандартных таблиц, чтобы пользователь их не видел и не мог поменять набор колоннок.

Можно и не убирать. Мне вообще нравиться вот такой вариант юзер кода:

var trader = new PlazaTrader();

trader.UsdOnlineListener.DataInserted += record =>
{
  ....
};

trader.StartListener(trader.UsdOnlineListener);

...

trader.StopListener(trader.UsdOnlineListener);

И для стандартных таблиц можно так же делать:

public override StartExport()
{
  StartListener(VcbFutureListener, CommonFutureListener, CommonOptionListener,...);
}
aspirant
aspirant

Mikhail Sukhov: Можно и не убирать. Мне вообще нравиться вот такой вариант юзер кода:

var trader = new PlazaTrader();

trader.UsdOnlineListener.DataInserted += record => ;

trader.StartListener(trader.UsdOnlineListener);

...

trader.StopListener(trader.UsdOnlineListener);


То есть получается пользователь не сможет создавать объекты типа PlazaListener?  И каждому классу-наследнику PlazaColumns (PlazaAggregationColumns и т.д.) внутри класса PlazaTrader будет соответствовать открытая переменная типа PlazaListener?

> Тем более что фильтры будут носить глобальный характер, т.е. вызвал RegisterSecurity(Security security) для какой-то бумаги, и все потоки сразу стали фильтровать только по ней, добавил еще одну - все потоки фильтруются по этим двум бумагам.
 
Я правильно понял?
Mikhail Sukhov
Mikhail Sukhov

aspirant: То есть получается пользователь не сможет создавать объекты типа PlazaListener? И каждому классу-наследнику PlazaColumns (PlazaAggregationColumns и т.д.) внутри класса PlazaTrader будет соответствовать открытая переменная типа PlazaListener?

Хм, насчет создания не знаю. Можно и оставить публичным конструктор. На всякий случай.

Каждому члену PlazaTableRegistry.

Тем более что фильтры будут носить глобальный характер, т.е. вызвал RegisterSecurity(Security security) для какой-то бумаги, и все потоки сразу стали фильтровать только по ней, добавил еще одну - все потоки фильтруются по этим двум бумагам.

Я правильно понял?

RegisterSecurity фильтрует только те потоки, которые относяться к инструментам. На потоки со сделками или стаканами он никак не влияет. То же самое справедливо и для других RegisterXXX

Mikhail Sukhov
Mikhail Sukhov

Mikhail Sukhov: RegisterSecurity фильтрует только те потоки, которые относяться к инструментам. На потоки со сделками или стаканами он никак не влияет. То же самое справедливо и для других RegisterXXX

И сразу код, как бы это можно было фильтровать:

var usdOnlineListener = trader.UsdOnlineListener;
usdOnlineListener.AddFilter(rih1);
...
usdOnlineListener.RemoveFilter(rih1);

Соответственно, реализация методов ITrader.RegisterXXX так же вызывает внутри себя AddFilter.

aspirant
aspirant

Mikhail Sukhov: И сразу код, как бы это можно было фильтровать:

var usdOnlineListener = trader.UsdOnlineListener; usdOnlineListener.AddFilter(rih1); ... usdOnlineListener.RemoveFilter(rih1);


Залил.  Посмотри.
Mikhail Sukhov
Mikhail Sukhov

aspirant: Залил. Посмотри.

Отредактировал. Кое что поменял. Например, идентификатор Плазы лучше засунуть по внутрь. Чтобы можно было без проблем его менять. Сейчас идентификатор составной из кода и класса для единообразия с остальными ITrader.

aspirant
aspirant

Mikhail Sukhov: Отредактировал. Кое что поменял. Все понятно.

Mikhail Sukhov:

aspirant: То есть получается пользователь не сможет создавать объекты типа PlazaListener? И каждому классу-наследнику PlazaColumns (PlazaAggregationColumns и т.д.) внутри класса PlazaTrader будет соответствовать открытая переменная типа PlazaListener?

Хм, насчет создания не знаю. Можно и оставить публичным конструктор. На всякий случай.

Каждому члену PlazaTableRegistry.

Вопрос: если мы оставляем конструктор публичным, и пользователь сможет сам создавать объекты типа PlazaListener, зачем тогда создавать открытые переменные для каждого члена PlazaTableRegistry? Не будет ди это дублированием схожего функционала? Либо пользователь сам подписывается на потоки через свои объекты PlazaListener, либо мы выдаем ему строгий набор переменных и они получает информацию из потоков через наши открытые переменные?

Mikhail Sukhov
Mikhail Sukhov

aspirant: Вопрос: если мы оставляем конструктор публичным, и пользователь сможет сам создавать объекты типа PlazaListener, зачем тогда создавать открытые переменные для каждого члена PlazaTableRegistry? Не будет ди это дублированием схожего функционала? Либо пользователь сам подписывается на потоки через свои объекты PlazaListener, либо мы выдаем ему строгий набор переменных и они получает информацию из потоков через наши открытые переменные?

Мне нравиться второй вариант.

aspirant
aspirant

Mikhail Sukhov: Отредактировал. Кое что поменял. Например, идентификатор Плазы лучше засунуть по внутрь. Чтобы можно было без проблем его менять. Сейчас идентификатор составной из кода и класса для единообразия с остальными ITrader.

В методе OnNewDataFromPosition нужно находить уже существующую бумагу, а известен только IsinId. Создавать еще один Dictionary, или все таки не использовать составной идентификатор. Кстати, такая же проблема будет встречаться в других потоках.

skuvv
skuvv Автор

aspirant:

Mikhail Sukhov: Отредактировал. Кое что поменял. Например, идентификатор Плазы лучше засунуть по внутрь. Чтобы можно было без проблем его менять. Сейчас идентификатор составной из кода и класса для единообразия с остальными ITrader.

В методе OnNewDataFromPosition нужно находить уже существующую бумагу, а известен только IsinId. Создавать еще один Dictionary, или все таки не использовать составной идентификатор. Кстати, такая же проблема будет встречаться в других потоках. Тут однозначно надо вначале формировать список инструментов, а потом плясать от него, те общий start делать внутри метода по этапам, например дождаться OnStreamDataEnd от инструментов и запустить остальные потоки данных

Mikhail Sukhov
Mikhail Sukhov

skuvv: Тут однозначно надо вначале формировать список инструментов, а потом плясать от него, те общий start делать внутри метода по этапам, например дождаться OnStreamDataEnd от инструментов и запустить остальные потоки данных

Сначала опишу проблему с идентификаторами Плазы. Если сейчас взять за основу IsinId и записывать его в Security.Id, то для таких инструментов не будет работать ни Гидра, ни тестирование на истории, так как S# использует единую систему именования Code@Class. Поэтому, мне кажется, что будет правильный вариант skuvv дождаться информации об инструменте, и только затем формировать производную информацию.

aspirant
aspirant

Mikhail Sukhov: Мне нравится второй вариант.

Тогда конструктор PlazaListener должен быть internal. Правлю?

aspirant
aspirant

Mikhail Sukhov:

skuvv: Тут однозначно надо вначале формировать список инструментов, а потом плясать от него, те общий start делать внутри метода по этапам, например дождаться OnStreamDataEnd от инструментов и запустить остальные потоки данных

S# использует единую систему именования Code@Class. Поэтому, мне кажется, что будет правильный вариант skuvv дождаться информации об инструменте, и только затем формировать производную информацию.

Насчет поэтапности - сделаем. Вопрос остается с IsinId. Посмотри еще раз реализацию OnNewDataFromPosition. Из потока приходит запись с IsinId бумаги. Ее класс (opt или fut) я не знаю. Именно поэтому я написал:

base.GetSecurity(CreateSecurityId(plazaId.ToString(), "InvalidClass")), // TODO Поменять

Я могу найти бумагу, например, так:

var sec = this.Securities.Where(x => x.ExtensionInfo["IsinId"].ToString() == "123456");

Но эту будет перебор списка, а не поиск по словарю. Или можно сделать по-другому?

aspirant
aspirant

Mikhail Sukhov: И каждому члену PlazaTableRegistry внутри класса PlazaTrader будет соответствовать открытая переменная типа PlazaListener

Добавил все переменные. Пока создаются со всеми колонками в методе CreateUserListeners().

Теперь: ты предлагаешь, чтобы при запуске PlazaTrader парсил файлы-схемы в заданной папке и создавал переменные PlazaListener с наборами колоннок, заданными пользователем. Вопрос: в каком месте пользователь будет задавать эти колоннки?

Mikhail Sukhov
Mikhail Sukhov

aspirant: Теперь: ты предлагаешь, чтобы при запуске PlazaTrader парсил файлы-схемы в заданной папке и создавал переменные PlazaListener с наборами колоннок, заданными пользователем.

Не PlazaListener, а PlazaTable, который передается в PlazaListener.

aspirant: Вопрос: в каком месте пользователь будет задавать эти колоннки?

У себя в коде. До Connect, я так понимаю (после подключение ведь нельзя уже править метаданные?)

Mikhail Sukhov
Mikhail Sukhov

aspirant: Но эту будет перебор списка, а не поиск по словарю. Или можно сделать по-другому?

Вопрос уже содержит ответ.🙂

aspirant
aspirant

Mikhail Sukhov:

skuvv: Тут однозначно надо вначале формировать список инструментов, а потом плясать от него, те общий start делать внутри метода по этапам, например дождаться OnStreamDataEnd от инструментов и запустить остальные потоки данных

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

Залил исправление. Кстати, дожидаться, похоже, нужно не OnStreamDataEnd, а OnPlazaStreamStateChanged, где state == TDataStreamState.DS_STATE_ONLINE. Может быть это из-за того, что сегодня выходной, но по потокам FORTS_FUTCOMMON_REPL\common и FORTS_OPTCOMMON_REPL\common, OnStreamDataEnd я так и не дождался.

aspirant
aspirant

Mikhail Sukhov: Вопрос уже содержит ответ.🙂

Кроме как создания дополнительного словаря, мне в голову больше ничего не приходит🤨

Mikhail Sukhov
Mikhail Sukhov

skuvv:

А зачем PlazaActionType заменил на i1 у колонки Action?

skuvv
skuvv Автор

Mikhail Sukhov:

skuvv:

А зачем PlazaActionType заменил на i1 у колонки Action? Ошибки схемы, PlazaActionType не работает error;p2err 32776=0x8008 - 'DB:WRONG_DB_SCHEME';Type mismatch for field 'action' in table 'orders_log'; server type i1; client type i4

Mikhail Sukhov
Mikhail Sukhov

Mikhail Sukhov:

skuvv:

А зачем PlazaActionType заменил на i1 у колонки Action? Ошибки схемы, PlazaActionType не работает error;p2err 32776=0x8008 - 'DB:WRONG_DB_SCHEME';Type mismatch for field 'action' in table 'orders_log'; server type i1; client type i4

Понятно. Но я думаю проблема вовсе не в PlazaActionType, так как он специально для этой колонки и создавался. Вопрос подозреваю уже к аспиранту.

aspirant
aspirant

Mikhail Sukhov:

aspirant: Теперь: ты предлагаешь, чтобы при запуске PlazaTrader парсил файлы-схемы в заданной папке и создавал переменные PlazaListener с наборами колоннок, заданными пользователем.

Не PlazaListener, а PlazaTable, который передается в PlazaListener.

aspirant: Вопрос: в каком месте пользователь будет задавать эти колоннки?

У себя в коде. До Connect, я так понимаю (после подключение ведь нельзя уже править метаданные?)

Вроде сделал, как ты предлагаешь. Старый код пока закомментил. Посмотри, пожалуйста. Папку с файл-схемами парсить не нужно.

Mikhail Sukhov
Mikhail Sukhov

aspirant: Вроде сделал, как ты предлагаешь. Старый код пока закомментил. Посмотри, пожалуйста. Папку с файл-схемами парсить не нужно.

А в чем необходимость PlazaSystemTableRegistry?

aspirant
aspirant

Mikhail Sukhov: А в чем необходимость PlazaSystemTableRegistry?

В нем таблицы, через которые мапятся стандартные объекты фреймворка. Мы должны быть уверены, что в стандартных (системных) таблица есть все колоннки. Если же использовать таблицу из PlazaTableRegistry возникнет проблема, если пользователь захочет указать в ней не все колоннки.

skuvv
skuvv Автор

Сделайте плиз OnNewDataFromPosition, у меня не получается 🤨

aspirant
aspirant

skuvv: Сделайте плиз OnNewDataFromPosition, у меня не получается 🤨

Пока сделал заглушку, чтобы код компилился. Есть вопрос к Михаилу + не знаю, что делать внутри лямбды Func<Portfolio, Security, Position> createPosition.

skuvv
skuvv Автор

Пробовал отправить ордера, получил эксепшн. В коде используется метод CreateMessageById, как оказалось по инфе суппорта функция пока не готова. Её и в документации нет. Переделаю на использование CreateMessageByName

skuvv
skuvv Автор

Исправил некоторые ошибки в отправлении ордеров, остановился в PlazaTrader в методе SendTransaction. В асинхронном режиме ошибка с _messageDispatcher. В синхронном режиме кажется неправильно используется reply.

aspirant
aspirant

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

  1. System.Runtime.InteropServices.InvalidComObjectException: по первой возникали при отписке от потоков, то есть при завершении работы. Я думаю, их стоит просто проглатывать.
  2. System.Runtime.InteropServices.COMException: возникали во всевозможных случаях, от тривиальных ошибок в файлах-схемах (с этим сейчас, надеюсь, не должно быть проблем), до всякой эзотерики, например, не запущен плазовский сервис Windows P2MQRouter.exe. Тут я вижу два варианта: 1) оборачивать в какой-нибудь InvalidOperation/ApplicationException или создавать открытое свойство-коллекцию ошибок. Прокидывать это исключение пользователю не хочется. Мы все-таки .Net-фреймворк, а не COM-библиотека? Что лучше?