Добрый день, Михаил! Как-то заметил, что стакан котировок отображается не совсем правильно - цена почему-то не снижается сверху вниз по всей глубине, а начинается с мимнимальной котировки и повышается сверху вниз по "биду", а в месте где начинается "аск" "переворачивается и снова начинается с самой маленькой котировки "аск" и так повышается до самого низа. Думал отсортировать данные на этапе прихода данных:
this.Trader.ProcessWellKnownDdeData += (name, dict) => // узнаем, что пришедшие данные отвечают за стакан if (name.Contains("stock")) // первичная сортировка по цене IEnumerable<Quote> _curquotes = (IEnumerable<Quote>)dict; _curquotes = _curquotes.OrderBy(t => t.Price); ... и т.д.
но это ни к чему не привело. Подскажите, пожалуйста, как правильно отсортировать данные?
Еще здесь был как-то уже вопрос про доступ к отдельным значениям котировок в стакане, но объяснения я так и не нашел. Как же все-такии это можно сделать (получить значение той или иной котировки в стакане для анализа)?
Kommentare (82)
Anmelden oder Konto erstellen, Melden Sie sich an oder registrieren Sie sich, um einen Kommentar zu hinterlassen
Какой пример? Sample?
Насчет Вашей сортировки _curquotes. А дальше вы куда присваиваете _curquotes?
Да, это в Sample. Собственно, на этом _curquotes и заканчиваются... Т.е. Вы намекаете на то, что таким образом коллекция, которая затем отображается в окне, осталась прежней? Как же перехватить исходные данные?
Я думаю это нужно вставить в SecuritiesWindow.
pair.Value.Quotes.AddRange(MainWindow.Instance.Trader.GetMarketDepth(pair.ey).OrderBy(t => t.Price).Select
Манируляции над IEnumerable не изменяют коллекцию, а возвращают новое значение. Собственно оно и содержит необходимые изменения.
Точно! Это сработало! Спасибо большое! :)
Интересно, а вот почему это работает - не понимаю. :) Ведь окно котировок в момент открытия окна "инструменты" еще не инициализировано. И экспорт "стакана" тоже еще не начал работать... А данная манипуляция с сортировкой происходит в "конструкторе", т.е. в момент создания экземпляра окна "инструменты". Как же это происходит?
А Вы посмотрите по-внимательнее. Там создается таймер, который из коллекции вытаскивает данные. И посмотрите, в какой момент эта коллекция заполняется.
Попытаюсь. Как мне кажется, что CreateTimer() запускается в конструкторе "инструментов" и потом работает постоянно каждую миллисекунду, правильно?
Определения CreateTimer() я нигде не нашел. В чем состоит его функция?
Он находится в Ecng.Common, TimerHelper. Создание таймера по входящему TimeSpan? Можно напрямую создавать через класс Timer, но через TimerHelper запись короче.
Добавил исключение в SecuritiesWindow() и обнаружил, что цикл внутри CreateTimer() вызывается как будто лишь при открытии стакана котировок и попытке начала экспорта стакана из квика. В другое время он значит не действует?
При открытии стакана добавляет элемент в коллекцию. А в таймере идет перечисление по коллекции. Поэтому, при ни одном открытом инструменте, таймер выполняет холостую работу.
Спасибо, теперь все гораздо яснее стало. Но при закрытии стакана, насколько я заметил, таймер продолжает работать, да?
Конечно. Таймер на окно с инструментами. У него может быть несколько стаканов, по одному на инструмент. Если бы он останавливался при закрытие одного стакана, другие бы тоже переставали работать.
Т.е. в данном случае таймер выполняет ту же функцию для стакана (импорт данных), что и подписка на события обновления котировок в других окнах (типа "инструменты")?
Не понял утверждения, если честно.
Имел ввиду следующее. Обновление окна "инструменты" - это работа this.Trader.NewSecurities += ..., а обновление окна "стакана" - это работа таймера.
Верно, только лучше так: а обновление окон "стаканов" - это работа таймера
Спасибо. С Вашей помощью туман успешно рассеивается. :)
Сейчас пытаюсь понять структуру данных в загружаемых коллекциях. Не могли бы популярно объяснить, как это работает. Как я понимаю, в конце "инструменты" создается экземпляр коллекции для будущих "стаканов":
private readonly SynchronizedDictionary<Security, QuotesWindow> _quotesWindows = new SynchronizedDictionary<Security, QuotesWindow>();
а при открытии окна котировок в конструкторе создается новый экземпляр но уже другой коллекции:
this.Quotes = new SynchronisedObservableList<SampleQuote>();
SynchronizedDictionary<Security, QuotesWindow> - заполняется котировками, а в чем состоит работа SynchronisedObservableList<SampleQuote>? Визуализация данных? И как они связаны между собой?
SampleQuote - это тот же Quote, только еще содержащий собственный объем.
SynchronisedObservableList сделал исключительно для
визуализации.
Вот тут написано как делать отображение данных в программе -
http://msdn.microsoft.com/ru-ru/library/ms752347.aspx
Для своих целей я добавил в SampleQuote дополнительные поля для вывода их в форме. Но никак не получается совладать с "разбором" коллекции. К примеру при формировании отображения в таймере я добавил строку для вывода минимальной цены стакана, чтобы потом ее присвоить к полю SampleQuote:
var _minprice = MainWindow.Instance.Trader.GetMarketDepth(pair.Key).Min(q => q.Price);
Но компилятор ругается, что "последовательность не содержит данных". Хотя в режиме отладки все переменные показывают значения. В чем тут ошибка?
GetMarketDepth - это стакан MarketDepth.
MarketDepth - это коллекция котировок. Ошибка говорит о том, что коллекция пуста. Такое бывает, когда нет экспорта по стакану (или он еще не стартанул).
С коллекцией котировок теперь тоже все понятно. :) Спасибо за полезные ссылки. Прочитал, все что было по отображению данных, но на практике ... никак не могу понять, почему эта конструкция работает:
<ListView ItemsSource="" GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler"> <ListView.View> <GridView> <GridViewColumn Width="70" Header="Bid" DisplayMemberBinding="" />
а вот эта (дополнительное текстовое поле в том же окне) нет:
<TextBox Name="AskBox" ... IsReadOnly="True" AcceptsTab="True" Grid.Column="1" Text=""/>
и так тоже нет:
<TextBox Name="BidBox" ... AcceptsTab="True" IsReadOnly="True" MinHeight="0" Grid.Column="1" Text="">
Best Bid - это дополнительное поле в SampleQuote. Причем если Best Bid загрузить в <GridViewColumn>, то данные нормально отображаются, а в текстовом поле нет. Это можно легко объяснить?
Похоже, объяснить оказалось нелегко. :) По крайней мере, для меня. Но после долгих "танцев с бубнами" удалось найти решение через обработку событий в коллекции Quotes. Судя по похожим проблемам на многих форумах. по-видимому, это был единственно возможный выход с данной версией WPF.
Нет, просто был занят релизом.
Text="" не выводится скорее всего потому, что не такого свойства. BestBid - это свойтство MarketDepth. А quotesWindow работаео к коллекцией SampleQuotes.
Text="" - не уверен что WPF понимает такие запросы. Я думаю, проще будет через форумы программерские спрашивать такие вещи. Напримерhttp://rsdn.ru/forum/dotnet.gui/
. Так как я и сам не особо спец в визуальной технологии.
Сейчас уже не очень актуально, т.к. решение по привязке уже есть и вроде как работает без сбоев. Как я это понял, проблема не в том, что WPF не понимает запросы - он как раз многое понимает - но не обновляет при обновлении коллекции - в этом я нашел причину и исправив ее через подписку на событие измения коллекции, все заработало.
Возник вопрос другого плана. При отправке своей заявки с помощью создания экземпляра класса NewOrderWindow возникает исключение от "Ecng.Trading.Quik.ApiException: Код ошибки: WrongSyntax Сообщение ACCOUNT=..." и т.д. При отправке из "Инструменты" заявка отправляется нормально, а из моего метода - нет, хотя - я проверил - синтаксис отправляемой заявки точно такой же! Отличие от отправки из окна "Инструменты" лишь в том, что я создаю новый пустой экземпляр Security и приписываю ему код бумаги и класс. В чем тут собака порылась?
А какой текст транзакции пр которой возникает ошибка?
Текст такой (интересно, есть способ как-то его скопировать, чтобы не переписывать? :)):
"ACCOUNT=SPBFUT...;CLIENT_CODE=XXX;TYPE=L;TRANS_ID=3;CLASSCODE=SPBFUT;SECCDE=GZM0;ACTION=NEW_ORDER;OPERATION=B;QUANTITY=10;PRICE=17331;EXECUTION_CONITION=PUT_IN_QUEUE;"
дальше идут:
в [] [] (Int32 [],StringBuilder []) в [] [] (String[], OrderStatus& [], Int32&[], Double& [], Strng& []) и т.д. там где [] - на самом деле квадратики...
Поскольку используется одна и та же функция NewOrder, то должно быть дело где-то в составляющих Order order, который параметром в RegisterOrder отправляется? Как же на самом деле формируется класс Order?
Пооблему удалось решить, послав в мой класс с методом ссылку на весь экземпляр Security и затем использовав его в формировании заявки. :) Ключевой момент - оказалось, что для формировании заявки только Security.code и Security.class - не достаточно. Что же там еще используется? Т.е. предыдущий вопрос все-таки остается - как формируется класс Order? Какие свойства Security он использует и как?
А если посмотреть под Debug режимом в студии эти свойства?
Да, что хорошо в Ваших уклончивых ответах - они стимулируют процесс познания. :)) Хоролшо бы еще разобраться как это сделать. :) До сих пор это слово - Debug - меня очень сильно пугало своей темной непостижимостью. Наверное, по нему где-то и проходит водораздел между настоящими программистами и любителями. :)
Ставится точка останова - F9. Запускается программа из студии через F5. Все, работаете в программе до тез пор, пока не сработает точка. Далее, на переменной мышкой и в контекстном меню выбираете нужное действие.
Спасибо, оказывается эта техника и называется Debug. :) Все оказалось не так страшно. Я и раньше этим пользовался. Правда, почему-то на точке останова программа не всегда останавливается. Или я что-то не учитываю ...
Есть такой вопрос. Стал переходить на 1.8. Выглядит еще более интересным и навороченным вариантом и поэтому не стал лениться и теперь все новые коды перевожу из 1.7 в 1.8. Вручную, правда, всегда есть риск что-то забыть. Существует ли какой-то алгоритм автоматического внесения изменений?
Автоматическое внесение изменений - это VS 2008 + руки. Это не шутка. Хотя все не так страшно. Я перевел свои разработки практически за день
Не увидел Вашего поста про ссылка на Security. Можете выделить в простое приложение и прислать если не будет работать. Как вариант, взять SampleConsonle
Да, за день я тоже управился с переводом своих кодов и уже добавил кучу новых. :) С ссылкой на Security все в порядке - там все работает. А сейчас плотно работаю с классом Strategy и возникает такая проблема: в моей стратегии на основе Strategy после подачи сигнала к покупке, делаю следующее:
this.Price, base.Volume); // регистрируем ее (обычным способом лимитированной заявкой) base.Trader.RegisterOrder(order); // добавляем зарегистрированную заявку в стратегию base.AddOrder(order);
т.е. все почти как в Вашем примере. Проверяю затем позицию:
Console.WriteLine(base.PositionManager.Position.ToString() + " | "+ this.PositionManager.Position.ToString());
и в итоге даже после совершения сделки там стоит "0". Почему так происходит? Как правильно отследить совершенную сделку?
Потом почему-то Strategy начинает самопроизвольно делать "отмену ордеров", которых и так нет, что приводит к появлению непрекрающегося потока сообщений об исключении от квика: "нельзя отменить заявку". Что может подавать такие заявки, base.AddOrder(order);?
Если включить сюда trycatch, то такие сообщения исчезают.
Нет ли возможности узнать подробнее "устройство" класса Strategy?
Код base.PositionManager.Position стоит в методе OnProcess(). Как я понял этот метод исполняется всякий раз с установленным интервалом (у меня 1 сек). Если он исполняется, то ведь и base.PositionManager.Position должен при каждой итерации воспроизводить то значение, которое у него устанавлено данным экземпляром стратегии? Может, я неправильно понимаю смысл слов "появления в программе сделки", но мое понимание, на основании предложенных примеров, такое: а) появление в программе сделки = запуск NewMyTrades, а) после запуска своей стратегии OnStrategyPropertyChanged меняет соответствующие свойства экземпляра стратегии, в т.ч. и свойства PositionManager.Position, которое должно обновляться после совершения сделки, б) _manager.Register(_strategy); запускает менеджер (правда, пока совсем не понимаю, что он конкретно делает, но я старался максимально придерживаться предложенного примера, поэтому оставил его в коде).
try-catch обернул первоначально base.Trader.RegisterOrder(order); и base.AddOrder(order); но сейчас понял, что отмену заявок скорее вызывает этот метод:
base.Orders.Where(o => o.State == OrderStates.Active).ForEach(base.Trader.GuarantyCancelOrder);
но ведь после совершения сделки в рамках стратегии в квике OrderStates.Active должен меняться соответственно? Т.е. если сделка фактически совершилась, то ведь кол-во активных заявок в стратегии должно каждый раз обнуляться? А получается, что это не так - в "активе" остается накопленное кол-во ОТПРАВЛЕННЫХ заявок, а это, как я понимаю, не то, для чего создаваось свойство OrderStates.
Мне кажется нужны более подробные объяснения работы класса, чтобы им можно было безопасно пользоваться. Или, как вариант, может, проще просто привести его код? :)
В инстуркции для примера у Вас написано: "Для учета общей прибыли- убытка (P&L) в торговом роботе необходимо использовать дочерние классы от BasePnLManager". В случае со стратегией - все понятно из примера. А как использовать другие дочерние классы? Например, как с помощью SecurityPnLManager рассчитать прибыль-убыток для конкретного инструмента?
var manager = new SecurityPnLManager(security, trader); manager.PnLChanged += ...
Спасибо. Наверное, следовало догадаться. :) В форуме где-то был аналогичный вопрос и как я понял, Ваши классы - менеджеры считают позиции только внутри дня. Т.е. они используют только внтуридневные данные "всех сделок" и "моих сделок"?
Да.
Не получается запустить StrategyPositionManager в окне QuotesWindow. Сделал так:
в поле:
private QuikTrader _trader = MainWindow.Instance.Trader;
в конструкторе:
SecurityPositionManager _secmanager = new SecurityPositionManager(this.Security, _trader); this.SecPos.Content = _secmanager.Position.ToString(); _secmanager.PositionChanged += new Action(_secmanager_PositionChanged);
обработчик:
void _secmanager_PositionChanged() { this.SecPos.Content = _secmanager.Position.ToString();
но при любом раскладе _secmanager.Position.ToString(); показывает 0. StrategyPositionManager при этом работает нормально. Что сделано неправильно?
this.Sync видели в примере? Я описал зачем это нужно в разделе Пользовательский интерфейс (GUI)
Не ругайтесь, я не программист, а только учусь. :) Пример читал, но видимо не совсем понял как работает this.Sync так скать физически. Если цель его - обновление пользовательского окна, то тогда я добавляю его к обработчику и получаю:
void _secmanager_PositionChanged() { this.Sync(() => ); } Делаю тестовый трейд, но позиция по-прежнему - 0... :(
А событие PositionChanged срабатывает? GetMyTrades(_secmanager.Security) что возвращает (скажем, если вызывать из ITrader.NewMyTrades)?
Похоже, что не вызывается почему-то. А GetMyTrades вызывает исключение "в экземпляре объекта не задана ссылка на объект", хотя _secmanager создается при открытии окна.
Посмотрите ошибку по детальнее. Скорее всего то она и виновата в причине не обновления позы.
Смотрю. При остановке программы в месте подписки на событие экземпляр _secmanager = null. Как это может быть, не те аргументы при создании SecurityPositionManager? Может, не тот шлюз? У меня шлюз тот же, что и в начале программы Sample, т.е. MainWindow.Instance.Trader.
Заметил, что в топике по аналогичному вопросу http://groups.google.ru/
group/stocksharp/browse_thread/thread/7334ab4cd0ed9bd9) уже задавлся этот вопрос. Но Ваш ответ там касался добавления заявки в стретагию (Strategy.AddOrder.), а вопрос был про SecurityPnLManager. Тогда такой вопрос - чем работа SecurityPnLManager отличается от StrategyPnLManager? Как я понимаю в случае с SecurityPositionManager и StrategyPositionManager ситуация аналогичная?
Все перепроверил еще раз: при создании экземпляра SecurityPositionManager его свойство Security остается = null, хотя в параметрах идет явная ссылка на экземпляр типа Security. Может, баг в конструкторе SecurityPositionManager?
У Вас другое - ошибка в обработчике. Какой у Вас лог ошибки?
Так обработчик не вызывается. События PositionChanged не происходит. а если использовать свойства экземпляра, то грит, что ссылка на пустой объект. secmanager.Security = null в любом месте кода.
При вызове _trader.MyTrades.Where(d => d.Order.State == OrderStates.Matched).Select(d => d) все заявки оказываются == Matched хотя в окне квика реально есть активные.
а
this.Secmanager = new SecurityPositionManager (q,_trader ); Secmanager.PositionChanged += new Action(_secmanager_PositionChanged);
реально обновляется только после работы стратегии, т.е. вместе со StrategyPositionManager. Хотя насколько я понимаю предполагалось, что он должен показывать реальную позицию по инструменту независимо от времени включения программы или работы стратегии?
GetMyTrades при этом выводит все сделки по инструменту нормально.
Это потому что Вы в него передаете null в качестве инструмента. Посмотрите в коде, почему так получается.
А как иначе, если у Вас написано "дайте все мои сделки, у которых состояние Matched и сконвертировать из MyTrade в MyTrade (что уж совсем бессмысленно)".
Правильно, но рассчитывает позицию он по таблице Мои Сделки. Там записи есть?
Так это читается "сконвертировать"! :) Везде в справочниках написано "проецировать" и оказывается я не так истолковал суть метода. Спасибо за очередную подсказку.
С null разобрался. Почему-то при передаче экземпляра класса Security через индексатор (get,set) он не сразу "активизируется" и при инициализации SecurityManager'а, соответственно, Security в его параметрах = null. Лишь передав параметр Security через конструктор SecurityManager получил "ненулевой" инструмент. Но почему оно так работает - пока не понял.
Да, в таблице "Мои сделки" есть записи. И сегодня при включении SecurityManager вроде начал давать правильную информацию. :) Но наблюдать пока продолжаю. :)
А при новом запуске программы SecurityManager.Position снова показывает 0.. В моих сделках запись при этом есть. SecurityManager нормально активизирован, инструмент "в нем" есть. SecurityManager.Trades = 1. Это правильно. А Position почему-то 0...
Это не индексатор, а свойство. И через него передать нельзя - оно private.
Версия 1.8?
Даже если у меня свойство было объявлено public?
Да, 1.8
У меня работает. Попробуйте еще вызвать метод Init
Да, с init функция заработала нормально. Спасибо.
Не получается добавить комментарий в заявку. Делаю так:
((QuikTrader)_trader).FormatTransaction += (str) => {str.SetComment(_currentoperationstring);}; base.Trader.RegisterOrder(order);
Но в квике все те же "ХХХ". Пробовал так:
order.Comment = _currentoperation; base.Trader.RegisterOrder(order);
Реультат тот же. Почему так получается?
http://groups.google.ru/group/stocksharp/browse_thread/thread/b4620292fdd85a53#
Т.е., чтобы изменить строку транзакции сначала надо убрать соотвествующую инструкцию, а потом заново ее написать? Типа
Да, все работает, только вместо TransactionBuilder.Comment почему-то надо использовать TransactionBuilder.ClientCode тогда "Комментарий" изменяется. А почему не свойство Comment отвечает за комментарий - ведь это логичнее?
Особенности настроек Квик сервера.
В связи с одним обсуждением (о задержке вывода стакана) возник вопрос: с какой целью в окне "инструменты" был создан таймер, если экспорт стакана можно просто запустить через методы RegisterQuotes и GetMarketDepth?
Кстати, GetMarketDepth.BestBid и GetMarketDepth.BestAsk показывают не лучший бид и аск соответственно, а вообще максимальную и минимальную цены стакана. По-моему, так не должно быть.
GetMarketDepth.BestBid и GetMarketDepth.BestAsk - а как Вы это видите?
GetMarketDepth.BestBid и GetMarketDepth.BestAsk получаю просто через GetMarketDepth(this.Security).BestBid.Price. В консоле эти значения соответствеено показывают для данного стакана минимальную и максимальную котировки соответственно.
Я начал тут "копать" по причине того, что уже много "наворотил" в своем GUI для управления стаканом. (Пока S#1.8) И недавно обнаружил, что при переходе на наиболее волатильные фьючи, типа RTS (до этого я тестировал все на сравнительно маловолатильных фьючерсах), стакан начинает резко тормозить. До запуска стакана вывод нормальный, а при запуске - начинается тормоз. Как будто что-то не успевает обрабатываться. Начал выводить данные в консоль - ситуация выглядит получше, но все равно - запаздывает на 5-10 секунд, а то и больше. Вот и ищу причину.
Про цикл не совсем понял. Если можно подписаться на событие GetMarketDepth.Changed и затем получить новые котировки, то зачем нужен цикл? (Или наоборот - если нужен цикл, то зачем нужно событие GetMarketDepth.Changed?) Наверное я не совсем правильно понимаю механику работы GetMarketDepth...
На GetMarketDepth.Changed можно подписаться. Но если открыть несколько стаканов - то ГУИ это убъет. GetMarketDepth.Changed вызывается очень часто. И в каждом из этих вызовов необходимо делать BeginInvoke (или Sync). А вот таймер всегда стабильно раз в несколько секунд, и не зависит от количество открытых стаканов.
Да, я уже понял, что ресурсов компьютера хватит для более-менее эффективной обработки лишь одного стакана. Наверное, надо переносить большую часть обработки "внутрь" процессов, а на ГУИ выводить лишь самое необходимое. Придется теперь подробнее заниматься производительностью кода. Или искать альтернативы DDE? Хотя, интересно, вот в самом же квике десятки тысяч данных обновляются без видимых затруднений - значит ли это, что у них технология лучше?
Столкнулся с таким сообщением от квика: "Вывод в wrapper по DDE приостановлен. Переполнена очередь сообщений." Что это значит?
Не обработки, а отрисовки. Обработка делается моментально. И как плюс, отказываться от сложного ГУИ. Я все по логам делаю.
По все видимости, принимающая сторона (робот) умерла.
Может, это отрисовка WPF оставляет желать лучшего? Windows.Forms не быстрее будет? Интересно, есть какие-нибудь исследования/рейтинги на этот счет?
Это лучше узнавать на программерских форумах - они то уж точно знают.
Интересно, а есть ли способ как-то протестировать время поступления какой-нибудь единицы данных? Скажем - от начала экспорта из квика до получения их на каком-нибудь выходе в ГУИ?
Я думаю только визуально. Писать в консоль время поступления данных, и смотреть при этом на ГУИ.
Сегодня на одном из этапов тестирования, когда котировки в очередной раз стали тормозить, случайно столкнулся с таким феноменом: квик в какой-то момент внезапно "вылетел", т.е. закрылся совсем, а в мою программу при этом продолжали поступать данные! Т.е. ощущение, что в DDE образовалась какая-то очередь, которую программа постепенно "пережевывала". Такое может быть? Т.е. эта очередь данных может реально где-то в памяти компьютера находится, пока не будет "потреблена"?
Да, очередь присутствует. И сколько по времени еще так жил экспорт?
Довольно долго. Я несколько минут ждал, но окончания так и не дождался...
Значит Квик не вылетел. Там максимум (при очень медленном компьютере) будет пару секунд.
Вылетел стопудово. И сообщение появилось о какой-то ошибке. Повторный запуск прошел нормально. А если квик в системе, то второй раз его не запустишь - появится соответствующее сообщение. Это случилось на фоне тормоза котировок стакана по волатеильному инструменту после запуска экспорта всех сделок. Я и сейчас отслеживаю котировки в консоли - они начинают сильно не совпадать с квиковскими имено после таблицы запуска всех сделок. И даже трудно понять, насколько сильно запаздывание, потому что котировки и там, и там весьма разные - визуально увидеть "хвост" не получается. Без "всех сделок" - все идет нормально.
Когда запускается Все сделки, то тормозить и должно. До минуты. Потому что данных много, и необходимо время на обработку. А вот после - должно работать как обычно.