Заметил пару нюансов по 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 тоже надо указывать в некоторых потоках.
- в PlazaStreamManager void Run() Не обрабатывает состояния типа DS_STATE_ERROR или DS_STATE_CLOSE, те в случае чего будет затык и поток будет висеть закрытым или с ошибкой. Я для себя брал реализацию с примера(могу выложить если что) PS У меня несколько сместились приоритеты - придется задержаться на квике, но постораюсь не пропадать.
Comentários (60)
Entrar ou Criar conta, Faça login ou cadastre-se para deixar um comentário
Я тоже брал реализацию с примера🙂. При тесте некоторые потоки открывались не с первого раза: затыков не было.
Вопрос по _PlazaTrader.StartListeners() Каким образом планируется добавлять потоки? Сейчас по общему типу PlazaMarketData. Не лучше ли явно задавать (StreamName,TableName,TRequestType) ? допустим все StreamName в списке - просто выбираем из enum списке, потом на основе выбронного StreamName, выбираем из Dictionary<enum StreamName,enum TableName> выбираем TableName подготавлием метаданные(default загатовку для соответсвующей TableName) и подписываем поток. StreamName и TableName подготовить не проблема и народу понятно по документации будет, что где. PS Средней руки юзер типа меня не осилит процесс подписки(то что идет внутри StartListeners()), слишком замудренно 😀
Это был пока черновой вариант. Сегодня появились мысли, как все это сделать попроще. Надеюсь выложить новую версию с комментариями в ближайшие дни.
Только что залил коммит:
В обоих случаях запрашиваются все колонки (поля) таблиц потоков Плазы. Если нужны не все колонки, можно использовать другой конструктор PlazaTable, в котором есть параметр IEnumerable<PlazaColumn> columns. Данные отлавливаются в событии public event Action<PlazaRecord[]> DataReceived; класса PlazaTable
Теперь насчет метода public void StartListeners(PlazaMarketData marketData). Он нужен для перегонки данных потоков в стандартные объекты, например создания коллекций инструментов и т.д., и поэтому нам необходимо контроллировать, какие подписки создаются и с каким набором колонок (полей). Любые другие предложения приветствуются.
С выбором Dictionary я ошибся... Тогда как вариант - единый enum список с названиями типа StreamName_TableName. Чтото Например FORTS_FUTTRADE_REPL_orders_log А уже на приеме парсить сроку, все стримы оканчиваются на REPL.
Я это и имел ввиду👍 в блоке ```
Потому что тот (не я 😀), кто расписывал класс PlazaFuturesSessionContentsColumns, забыл исправить название поля Плазы, когда копировал переменные:
Я только что залил исправление.
При тестировании нужно будет проверить каждый класс в папке Metadata (создать из них таблицы Плазы). Я, когда тестировал алгоритм подключения к потокам, тоже натыкался на пару схожих ошибок.
this.StepPrice = new PlazaColumn(PlazaTableRegistry.FutSessionContents, "min_step", typeof(decimal), "d16.5", true);
потоки FuturesAggregation не работают(не получают данные после выхода в онлайн режим), в логах клиента error: есть подозрение на <mark>lifenumber</mark> метатаблицы
Пофиксил _dataStream.Close(); не нужно в OnStreamLifeNumChanged
Проверил и пофиксил все таблицы(метаданные) начинающиеся на Futures. Можно принимать.
События OnNewDataFromFuturesSessionContents и OnNewDataFromOptionSessionContents иногда не заполняют полностью Security - только создается new Security по id. Возможно метод GetSecurity не thread safe
Больше вероятность, что при инициализации происходит ошибка. А GetSecurity сначала добавляется Security, и только уже потом вызывается делегат на заполнение полей. Вот там видимо и падает. Подозреваю, что не все понял проводятся. Например:
может упасть, если неправильный тип данных (например, РТС прислала пустое значение). Посмотрите на ProcessDataError.
Еще: надо было мне конечно предупредить, что я еще не дописал методы StartExport и StopExport. Сейчас, когда начал заливать PlazaTrader, увидел обновления skuvv'a. Вроде все вместе смержил.
Вопросы: все стандартные потоки добавлены?
Предложенная skuvv'ом фильтрация подойдет? Я вроде видел, что у PlazaTrader есть свой стандартный интерфейс влкючения/выключения фильтрации (RegisterSecurity/UnregisterSecurity)?
Мне не нравиться:
Все методы по переводу в и из плазовский формат нужно переносить в специальный класс - PlazaFormatter.
Работа с PlazaRecord требует рефакторинг (прямо таки кричит):
...Это кто так учил писать?🙂 Нужно заменить на Dictionary.PlazaColumnList.Names не нужно ни в коем случае. Мы не работает со строчками. Мы работает с колонками - PlazaColumn. Соответственно, ключом в _data должен быть не string а PlazaColumn.
Индексаторы у PlazaRecord не нужны. Лучше сделать в нем метод вида T Get(PlazaColumn), который бы конвертировал значение и при возникновении ошибки, перехватывал бы ее и выбрасывал свою с описанием того, что за колонка, что пришло на вход. Так пользователю будет проще понять ошибку (да и нам).
[*]Опять возвращаемся к метаданным. Не нужно передавать в PlazaListener PlazaColumns. PlazaColumns - это абстрактный базовый класс для всех колонок. Для ограничения колонок необходимым нам множеством полей нужно использовать PlazaTable.Columns. Еще раз. PlazaTable - это как раз те настройки, которые описываю наши схемы на клиенте. PlazaColumns - базовый вспомогательный класс. PlazaColumnRegistry - коллекция всех возможных колонок. PlazaTableRegistry - коллекция всех возможных таблиц.
Добавил для плазовской даты. Так подойдет:
Знаю, что кричит. Но когда ключом был string, нужно было использовать такого монстра, чтобы работал индексатор по номеру колонки. Dictionary отсортировал бы свои элементы по алфавиту (ключи ведь string).
Сейчас я переделал на Dictionary с ключом PlazaColumn. Крик ушел🙂
Получается, ты делал свою реализацию PlazaTrader?
Ты путаешь с SortedDictionary. И то там не по алфавиту... Это хеш алгоритм. Там сортировка не нужна. Группировка значения идет по хэшу ключа. Соответственно, когда обращаемся по ключу, сначала берется его хэш (PlazaColumn.GetHashCode) и получаются все значения, соответствующие хэшу. Операция O(1). И далее, если у нас коллизия, перебором всех ключей идет сравнение (PlazaColumn.Equals) и находится первый попавшийся (ага, уникальность там не проверяется).
Итого, в лучшем варианте O(1). В худшем O(n). Но чтобы добиться последнее, надо еще постараться.🙂
Да, насчет сортировки я был не прав.
Посмотрел и доделал кое-что.
Рассмешило название PlazaPartColumns. Долго думал, что за "часть". Оказалось, это Партфэл, дарагой!
Что осталось до переделать:
Может выложить это ввиде отдельного теста для сравнения перфоманса? Насчет OnNewSecurityChanged - конечно. Если вместо поиска по словарю устраивать Exist Find и т.д., то я не удивлюсь и пол минутными задержками.🙂
Вообще PlazaTable сейчас и есть то, что ты предлагаешь. За исключением полей метаданных все остальные методы закомментены (причем, делал это не я). Можно сделать вот так:
Event datainserted Все типы таблицы я получал в соответсвующих им win thread. Событий никаких нет 😀, заполняется статик массив instruments,а роботы на каждом прогоне его чекают. Ну и в конце запись в текстовики, через самописный логгер. ps фильтрация опять же массиву instruments
Взял за основу отсюда:
Ок, не поняли друг друга. Тогда ремарка и код. Если enum используется для задания строковых констант, то используется класс со строками:
На этом примере сразу видно недостаток именования перечислений (не ReplicationStreams, а ReplicationStream). Сходу кажется, что идет работа с потоком. А на самом деле к потокам это имеет о посредственное значение.
// вместо: public TableName SystemName { get; private set; } public string SystemName { get{ _systemName.ToString(); }
Вопросы:
static class TableName...
Что делать с нашими стандартными потоками, которые нужны, чтобы мапить объекты. Мы договорились, что их скроем от клиента, чтобы гарантировать в них нужный набор колоннок. Придется все равно создавать какие-то отдельные таблицы.
Давай еще раз обсудим. С точки зрения практики - зачем это нужно?
Хм, а почему бы просто не оставить как есть сейчас?
Сегодня думал на эту тему. Пожалуй, действительно ты прав: нужно упростить систему. Тем более что фильтры будут носить глобальный характер, т.е. вызвал RegisterSecurity(Security security) для какой-то бумаги, и все потоки сразу стали фильтровать только по ней, добавил еще одну - все потоки фильтруются по этим двум бумагам. Я правильно понял? Вопрос: события, связанные с приходом данных (DataInserted, DataDeleted и т.д.), перекидываю из PlazaListener в PlazaTable? Или всё в PlazaTrader, и добавляю в события первым параметром PlazaTable, чтобы можно было различать, из какой таблицы пришли данные? Больше нравится первое.
Можно и не убирать. Мне вообще нравиться вот такой вариант юзер кода:
И для стандартных таблиц можно так же делать:
var trader = new PlazaTrader();
trader.UsdOnlineListener.DataInserted += record => ;
trader.StartListener(trader.UsdOnlineListener);
...
trader.StopListener(trader.UsdOnlineListener);
Хм, насчет создания не знаю. Можно и оставить публичным конструктор. На всякий случай.
Каждому члену PlazaTableRegistry.
Я правильно понял?
RegisterSecurity фильтрует только те потоки, которые относяться к инструментам. На потоки со сделками или стаканами он никак не влияет. То же самое справедливо и для других RegisterXXX
И сразу код, как бы это можно было фильтровать:
Соответственно, реализация методов ITrader.RegisterXXX так же вызывает внутри себя AddFilter.
var usdOnlineListener = trader.UsdOnlineListener; usdOnlineListener.AddFilter(rih1); ... usdOnlineListener.RemoveFilter(rih1);
Отредактировал. Кое что поменял. Например, идентификатор Плазы лучше засунуть по внутрь. Чтобы можно было без проблем его менять. Сейчас идентификатор составной из кода и класса для единообразия с остальными ITrader.
Вопрос: если мы оставляем конструктор публичным, и пользователь сможет сам создавать объекты типа PlazaListener, зачем тогда создавать открытые переменные для каждого члена PlazaTableRegistry? Не будет ди это дублированием схожего функционала? Либо пользователь сам подписывается на потоки через свои объекты PlazaListener, либо мы выдаем ему строгий набор переменных и они получает информацию из потоков через наши открытые переменные?
Мне нравиться второй вариант.
В методе OnNewDataFromPosition нужно находить уже существующую бумагу, а известен только IsinId. Создавать еще один Dictionary, или все таки не использовать составной идентификатор. Кстати, такая же проблема будет встречаться в других потоках.
Сначала опишу проблему с идентификаторами Плазы. Если сейчас взять за основу IsinId и записывать его в Security.Id, то для таких инструментов не будет работать ни Гидра, ни тестирование на истории, так как S# использует единую систему именования Code@Class. Поэтому, мне кажется, что будет правильный вариант skuvv дождаться информации об инструменте, и только затем формировать производную информацию.
Тогда конструктор PlazaListener должен быть internal. Правлю?
Насчет поэтапности - сделаем. Вопрос остается с IsinId. Посмотри еще раз реализацию OnNewDataFromPosition. Из потока приходит запись с IsinId бумаги. Ее класс (opt или fut) я не знаю. Именно поэтому я написал:
Я могу найти бумагу, например, так:
Но эту будет перебор списка, а не поиск по словарю. Или можно сделать по-другому?
Добавил все переменные. Пока создаются со всеми колонками в методе CreateUserListeners().
Теперь: ты предлагаешь, чтобы при запуске PlazaTrader парсил файлы-схемы в заданной папке и создавал переменные PlazaListener с наборами колоннок, заданными пользователем. Вопрос: в каком месте пользователь будет задавать эти колоннки?
Не PlazaListener, а PlazaTable, который передается в PlazaListener.
У себя в коде. До Connect, я так понимаю (после подключение ведь нельзя уже править метаданные?)
Вопрос уже содержит ответ.🙂
Залил исправление. Кстати, дожидаться, похоже, нужно не OnStreamDataEnd, а OnPlazaStreamStateChanged, где state == TDataStreamState.DS_STATE_ONLINE. Может быть это из-за того, что сегодня выходной, но по потокам FORTS_FUTCOMMON_REPL\common и FORTS_OPTCOMMON_REPL\common, OnStreamDataEnd я так и не дождался.
Кроме как создания дополнительного словаря, мне в голову больше ничего не приходит🤨
А зачем PlazaActionType заменил на i1 у колонки Action?
Понятно. Но я думаю проблема вовсе не в PlazaActionType, так как он специально для этой колонки и создавался. Вопрос подозреваю уже к аспиранту.
Вроде сделал, как ты предлагаешь. Старый код пока закомментил. Посмотри, пожалуйста. Папку с файл-схемами парсить не нужно.
А в чем необходимость PlazaSystemTableRegistry?
В нем таблицы, через которые мапятся стандартные объекты фреймворка. Мы должны быть уверены, что в стандартных (системных) таблица есть все колоннки. Если же использовать таблицу из PlazaTableRegistry возникнет проблема, если пользователь захочет указать в ней не все колоннки.
Сделайте плиз OnNewDataFromPosition, у меня не получается 🤨
Пока сделал заглушку, чтобы код компилился. Есть вопрос к Михаилу + не знаю, что делать внутри лямбды Func<Portfolio, Security, Position> createPosition.
Пробовал отправить ордера, получил эксепшн. В коде используется метод CreateMessageById, как оказалось по инфе суппорта функция пока не готова. Её и в документации нет. Переделаю на использование CreateMessageByName
Исправил некоторые ошибки в отправлении ордеров, остановился в PlazaTrader в методе SendTransaction. В асинхронном режиме ошибка с _messageDispatcher. В синхронном режиме кажется неправильно используется reply.
Вопрос по COM исключениям, которые могут возникать в PlazaStreamManager'е. По результатам небольшого тестирования возникают два: