понедельник, 30 ноября 2009 г.

Ecng.Trading 1.3

Выложил новую версию Ecng.Trading v1.3. Скачивать отсюда. Теперь по этой ссылке я буду выкладывать все новые релизы.

В новой версии я добавил два, на мой взгляд, главных изменения.

Первое касается того, как экспортировать произвольные таблицы из Квика. В QuikTrader появилось событие ProcessDdeData. Данное событие вызывается тогда, когда Квик послал DDE данные, которые QuikTrader не умеет обрабатывать. В качестве демонстрации я добавил в свое приложением-пример отображение данных по портфелю. Сначала я настроил Квик, добавив в него соответствующую таблицу:



Обновленные info.wld файл идет вместе с архивом.

Затем, добавил в него код обработки и отображения данных:

_trader.ProcessDdeData += (name, rows) =>
{
// узнаем, что пришедшие данные отвечают за портфель
if (string.Compare(name, "portfolio", true) == 0)
{
foreach (var row in rows)
{
var client = (string)row[0];
var portfolio = _portfolioWindow.Portfolios.FirstOrDefault(p => p.Client == client);

if (portfolio == null)
{
portfolio = new Portfolio { Client = client };
_portfolioWindow.Portfolios.Add(portfolio);
}

portfolio.Shorts = (double)row[1];
portfolio.Longs = (double)row[2];
portfolio.Collateral = (double)row[3];
portfolio.Margin = (double)row[4];
portfolio.Money = (double)row[5];
portfolio.PnL = (double)row[6];
}
}
};

Так как QuikTrader уже содержит методы по запуску и остановке DDE экспорта (StartDde и StopDde), то для удобства я перегрузил этим методы, чтобы они могли принимать имя экспортируемой таблицы (имя отображается в заголовке таблицы в Квике). Это позволит не только управлять своим потоком данных, но еще и запускать и останавливать его. Так что, если кто-то еще разрабатывает роботов под Excel, руководствуясь тем, что в него можно передавать данные как угодно, знайте, теперь это можно делать и в Ecng.Trading. Ну, а про преимущества разработки роботов на C# по сравнению с Excel я уже писал здесь.

Второе изменение знаковое. Наконец-то, в Ecng.Trading появился первый торговый алгоритм - котирование заявок. Например, необходимо срочно закрыть позицию, продав или купив по рынку (при минимуме потере профита). При высоколиквидном инструменте посланная из Квика или робота заявка может дойти до рынка уже "неактуальной" (в принципе, можно выставлять и из Квика рыночную заявку, но она работает не на всех биржах). Чтобы решить эту проблему, я написал класс MarketOrderRegistry, в который добавляется заявка. Заявка может быть и как уже ранее зарегистрированная, так и "пустая" (только что созданная и еще не выпущенная на биржу). И уже этот класс двигает эту заявку в стакане так, чтобы продать ее по выгодной рыночной цене. Рыночная цена опирается на текущий BestBid и BestAsk инструмента. Если же задана MarketDelta (передается в метод AddOrder), то цена заявки будет высчитываться как смещение цены последней сделки на эту самую MarketDelta.

Другое применения MarketOrderRegistry - это скальперская стратегия. Класс может сам создавать заявки и контролировать их в стакане.

Сам по себе класс MarketOrderRegistry построен на основе методов из класса TraderHelper:

1. GuarantyCancelOrder - гарантированно отменить заявку. В цикле посылает команду CancelOrder до тех пор, пока заявка не снимется (или не исполниться, если робот не успел снять).
2. ReRegisterOrder - перерегистрировать заявку. Удобен тем, что умеет подстраиваться под особенности биржи. Например, FORTS умеет изменять заявки одной транзакцией. Тогда в метод нужно передавать параметр isForts равный true. Если биржа не поддерживает изменения заявки одной транзакцией, то метод последовательно сначала снимает заявку через GuarantyCancelOrder, а затем регистрирует новую.

В принципе, можно реализовать своего собственного котировщика, вызывая методы TraderHelper. Как я уже упоминал, для той же скальперской стратегии. Сам по себе MarketOrderRegistry не смотрит на то, действительно ли нужно переставлять заявку. Он лишь старается выставить ее на край спреда. Поэтому, низкоуровневый класс TraderHelper для тех, кто реализует скальпинг, будет более полезен, чем сам MarketOrderRegistry. Используя TraderHelper можно написать логику лучше заявки (например, основываясь на ценовом или количественном объеме впереди в стакане).

36 комментариев:

  1. Смотрел Order.Condition

    Вот фрагмент кода, подправте пожалуйста если что не так

    //Создаем стоп-заявку на покупку
    Order order = new Order
    {
    Price = _settings.Security.BestAsk,
    Account = _settings.Account,
    Type = OrderTypes.Conditional,
    Volume = _settings.Volume,
    Direction = OrderDirections.Buy,
    Security = _settings.Security
    };

    order.Condition.StopPrice = _settings.Security.BestAsk+2;

    ОтветитьУдалить
  2. Этот комментарий был удален автором.

    ОтветитьУдалить
  3. Да, закралась ошибка. Библиотека должна отделять тейк от стоп-лимита по цене (та, которая Order.Price но не Order.Condition.StopPrice). Если указана, то лимит, иначе тейк. Но не определяет. Выложу новую версию через несколько часов в тот же архив.

    ОтветитьУдалить
  4. Обновил архив с версией 1.3. Попутно сделал некоторые небольшие и большие изменения. Напишу о них по позднее.

    ОтветитьУдалить
  5. Здравствуйте, а можно посмотреть реализацию дде сервера... никак понять не могу как с ним работать) Спасибо

    ОтветитьУдалить
  6. Mika, я заметил, что отправка транзакций по API синхронные, а есть ли возможность посылать асинхронные заявки?

    ОтветитьУдалить
  7. Mika.
    Я обновил билиотеки, но все еще проблема существует.

    Вот фрагмент кода, подправте пожалуйста если что не так

    //Создаем стоп-заявку на покупку
    Order order = new Order
    {
    Price = _settings.Security.BestAsk,
    Account = _settings.Account,
    Type = OrderTypes.Conditional,
    Volume = _settings.Volume,
    Direction = OrderDirections.Buy,
    Security = _settings.Security
    };

    order.Condition.StopPrice = _settings.Security.BestAsk+2;

    А вот строка транзакции
    ACCOUNT=ХХХХХХХХХ; CLIENT_CODE=XXX; TRANS_ID=1; CLASSCODE=SPBFUT; SECCODE=SRZ9; ACTION=NEW_STOP_ORDER; OPERATION=S; QUANTITY=1; STOPPRICE=7191; PRICE=7193; STOP_ORDER_KIND=TAKE_PROFIT_STOP_ORDER;

    ОтветитьУдалить
  8. В строке транзакции должны быть и STOPPRICE=7191; и PRICE=7193;
    А не лудше програмисту решать какой тип сделки он хочет и самому выбирать STOP_ORDER_KIND?

    ОтветитьУдалить
  9. HaMMeR,

    Возьмите еще раз послед. версию. Накладка получилась в спешке - перезалил старые dll. Теперь все работает, в частности Ваш примерно у меня прошел.

    ОтветитьУдалить
  10. Gazrvs,

    Легко. Пользуемся мощными .NET средствами:

    var order = new Order();
    // создаем делегат
    Action action = () => _trader.RegisterOrder(order);
    // вызываем его асинхронно
    action.BeginInvoke(result =>
    {
    // когда мы тут, то заявка уже зарегистрированна
    Console.WriteLine(order.Id);
    // очищаем хэндл асинхронного вызова
    action.EndInvoke(result);
    }, null);

    ОтветитьУдалить
  11. Serg,

    В примере этого поста я как раз и показал, как с ним работать.

    ОтветитьУдалить
  12. Mika.
    Спасибо, что подправили Стоп-Заявки.
    Но метод
    public override void CancelOrder( Ecng.Trading.BusinessEntities.Order order)
    Member of Ecng.Trading.QuikWrapper.QuikTrader

    не умеет распознавать и снимать Стоп заявки.

    ОтветитьУдалить
  13. HaMMeR,

    В ближайшее время (до четверга) выложу следующую версию. Там будет полная поддержка стоп-заявок (регистрация, снятие, изменение). В том числе и DDE. В том числе и события NewStopOrders, ChangedStopOrders. В том числе и так же возможность получить дочерние заявки по родительской стоп-заявке.

    ОтветитьУдалить
  14. Доброе время суток!
    Попробовал запустить пример с экспортом всех сделок и получил такую ошибку
    Не удалось привести тип объекта "System.Double" к типу "System.String".
    Ecng.Trading.QuikWrapper
    в Ecng.Trading.QuikWrapper.QuikTrader.☻(String ☻, IList`1 ♥)
    в ☻.♥()

    Что можно сделать?
    Запускаю с версией quik 5.14, Vista, Framework 3.5

    ОтветитьУдалить
  15. Попробовал еще экспортировать список инструментов и получил такое
    Заданное приведение является недопустимым.
    Ecng.Trading.QuikWrapper
    в Ecng.Trading.QuikWrapper.QuikTrader.♠.☻(String ☻)
    в Ecng.Collections.CollectionHelper.SafeAdd[K,V](IDictionary`2 dictionary, K
    key, Func`2 handler)
    в Ecng.Trading.QuikWrapper.QuikTrader.☻(String ☻, IList`1 ♥)
    в ☻.♥()

    ОтветитьУдалить
  16. Leonandr, Андрей Леонтьев,

    Видимо, таблицы в Квике настроены не так, как в нужно. Посмотрите скриншоты, которые идут в архиве. Все ли у вас колонки? В правильно ли порядке?

    ОтветитьУдалить
  17. да, загрузил файл info.wnd и все заработало. А в чем секрет? только в наличии таблиц и нужных колонок или еще где-то есть какие-то важные настройки квика?

    ОтветитьУдалить
  18. только в наличии таблиц и нужных колонок

    ОтветитьУдалить
  19. Тут уже писали про исключение The service is not registered. Оно появляется тут 9 из 10 раз.
    trader = new QuikTrader(PATH_2_QUIK, "wrapper",
    "Вывод через DDE сервер", "инструменты", "все сделки", "заявки", "мои сделки", "{0} котировки");

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

    ОтветитьУдалить
  20. да, версия самая последняя.
    Ошибка появляется, если я вешаю обработчик на ConnectionChanged

    trader = new QuikTrader(PATH_2_QUIK, "wrapper",
    "Вывод через DDE сервер", "инструменты", "все сделки", "заявки", "мои сделки", "{0} котировки");

    trader.ConnectionChanged += connChanged => Sync(() =>
    {
    String m = connChanged.Message;
    });

    ОтветитьУдалить
  21. А что пишет, если подписаться на DdeError?

    ОтветитьУдалить
  22. все работает, прошу прощения.

    ОтветитьУдалить
  23. Добрый вечер.
    Теперь другая проблема. Экспортирую стакан котировок, а при чтении его в программе, в поле Volume, если OrderDirection = Sell, всегда стоит 0.
    Наверное неправильно настроил таблицу стакана, но непонятно как сделать это правильно.

    ОтветитьУдалить
  24. А кспортируете как? Сами, через ProcessDdeData или через ITrader.GetQuotes? Во втором случае, скриншот с правильной настройкой лежит в архиве. Называется quote_dde.png

    ОтветитьУдалить
  25. Экспортирую через ITrader.GetQuotes. Но все равно поле volume равно 0 для продажи. А для покупки все нормально. Очень странно.
    К тому же, мне кажется, что перепутаны sell и buy.
    Вот как у меня выводится.

    Price Volume Direction
    1696,16 0 Sell
    1696,17 0 Sell
    1696,18 0 Sell
    1697 0 Sell
    1697,2 0 Sell
    1697,57 0 Sell
    1698 0 Sell
    1698,22 0 Sell
    1698,36 0 Sell
    1699 0 Sell
    1699,02 13 Buy
    1699,33 1 Buy
    1700 11 Buy
    1700,06 1 Buy
    1700,13 25 Buy
    1700,14 133 Buy
    1700,15 73 Buy
    1700,21 12 Buy
    1700,26 66 Buy
    1700,27 18 Buy

    ОтветитьУдалить
  26. А стакан настроен как на картинке?

    ОтветитьУдалить
  27. Mika, вы пользуетесь ли каким нибудь дополнительным приложением(или плагином)к Visual Studio для работы с WPF, что то запутался с привязкой данных кода к новому окну WPF!?

    ОтветитьУдалить
  28. А когда будет полная поддержка Стоп-заявок?

    ОтветитьУдалить
  29. HaMMeR, выложил. Описание будет чуть позже. В DDE поменялась таблица Заявки и появилась новая Стоп-Заявки. Все отражено на скриншотах.

    ОтветитьУдалить
  30. Leonandr,

    Попробуйте с теми стаканами, что уже созданы в моем wnd файле.

    ОтветитьУдалить
  31. Gazrvs,

    WPF - это граф. библиотека. Дополнительно использую использую WPF Toolkit. Последнее никокого оотношения к привязке данных (binding) не имеет, только новые контролы. В новом примере я использую из WPF Toolkit DatePicket контрол для выбора срока действия стоп-заявки.

    ОтветитьУдалить
  32. Mika,

    К сожалению не объемы с Sell не выводятся ни с какими стаканами (даже с теми, что присутствуют в вашем файле). Уже какими только способами не пробовал, ничего не помогает. Жаль, что в примере нет окна с котировками, очень помогло бы. Во всяком случае я поверил бы, что проблема именно в моем коде.

    ОтветитьУдалить
  33. Leonandr,

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

    ОтветитьУдалить