пятница, 18 сентября 2009 г.

QuikWrapper

Обновление! Данный проект был полностью переделан. Описание нового проекта с документацией и дистрибутивом находиться по адресу http://stocksharp.com.

На днях я, наконец-то, разобрался с системой экспорта данных из Quik по DDE (результаты качать отсюда). Парадокс построения МТС на базе Quik таков, что довольно легко отправить в него данные (заявки), но сложно получить что-то обратно (информацию по инструментам, тикам и т.д.). Стандартно механизм отдачи данных внешней программе построен на специальных адаптерах (для AmiBroker и Wealth-Lab идут стандартно в поставке), но если нужно действительно автономное решение (программирование роботов на C#, Java или Delphi), то начинаются танцы с бубном. Для сведения, протокол DDE - это старая технология. Настолько старая, что мало того, на данный момент в новых средах программирования (.NET или Java) не существует стандартных компонентов для работы с DDE, так еще и описаний в инете практически не найти.

В принципе, экспорт данных из Quik можно настроить через ODBC. Но у него есть ряд своих недостатков. Во-первых, на компьютере должна быть установлена база данных (мой выбор, MS Sql Server 2008 Express - быстр, стабилен, много возможностей, и, конечно же, бесплатен). Во-вторых, это медленная передача данных. В отличие от DDE, где данные передаются напрямую от Quik к сторонней программе, использование промежуточной базы данных сильно затормаживает и скорость самой МТС.

Есть еще последний вариант, когда можно написать МТС внутри Quik-а на языке QPile, но я его не рассматривал, так как это очень медленное решение, вдобавок и ограниченное возможностями самого языка (на дворе 21-ый век, и нужно использовать компьютеризированные возможности по максимуму).

Собственно, для того, чтобы каждый раз новичкам не проходить тернистый путь написания МТС на языке C# (или любом другом под платформу .NET), я и написал библиотеку QuikWraper. За основу был взят пример работы с Quik API (скачать можно отсюда http://www.quik.ru/user/download/, называется "API импорта транзакций"). В итоге получился следующий функционал:
/// 
/// Основной класс, предоставляющий шлюз взаимодействия с Quik.
/// 
public class Trader
{
/// 
/// Инициализация класса Trader.
/// 
/// Путь к директории, где установлен Quik./// Название DDE сервера.public Trader(string path, string ddeServer);

/// 
/// Событие изменения состояния подключения. Срабатывает при первом подключении программы к Quik-у,
/// сигнализирую о том, что соединение установлено.
/// 
public event Action ConnectionChanged;

/// 
/// Ошибка при обработке DDE данных, посланых Quik-ом.
/// 
public event Action DdeError;

/// 
/// Событие появления собственных новых сделок.
/// 
public event Action> NewMyTrades;

/// 
/// Событие появления всех новых сделок.
/// 
public event Action> NewTrades;

/// 
/// Событие появления новых заявок.
/// 
public event Action> NewOrders;

/// 
/// Событие изменения состояния заявки (снята, удовлетворена).
/// 
public event Action> OrdersChanged;

/// 
/// Событие загрузки данных по инструментам.
/// 
public event Action SecuritiesLoaded;

/// 
/// Список всех загруженных инструментов.
/// Вызывать только после того, как пришло событие .
/// 
public IEnumerable Securities;

/// 
/// Получить биржевое время.
/// 
public DateTime StockTime;

/// 
/// Получить все зявки, которые были зарегистрированный программой через метод .
/// 
public IEnumerable Orders;

/// 
/// Получить мои сделки.
/// 
/// Инструмент, по которому нужно найти сделки./// Дата, с которой нужно искать сделки./// Дата, до которой нужно искать сделки./// Найденные сделки.
public IEnumerable GetMyTrades(Security security, DateTime from, DateTime to);

/// 
/// Получить все сделки.
/// 
/// Инструмент, по которому нужно найти сделки./// Дата, с которой нужно искать сделки./// Дата, до которой нужно искать сделки./// Найденные сделки.
public IEnumerable GetTrades(Security security, DateTime from, DateTime to);


/// 
/// Получить стакан котировок.
/// 
/// Инструмент, по которому нужно получить котировки./// Найденные котировки. Если для инструмента нет котировок, то возвращается пустой список.
public IEnumerable GetStock(Security security);

/// 
/// Зарегистрировать заявку на бирже.
/// 
/// Заявка, содержащая информацию для регистрации.public void RegisterOrder(Order order);

/// 
/// Отменить заявку на бирже.
/// 
/// Заявка, которую нужно отменять.public void CancelOrder(Order order)

/// 
/// Получить заявку по сделке.
/// 
/// Сделка, по которой нужно искать заявку./// Найденная заявка.
public Order GetOrder(Trade trade);
}

В качестве теста я создал консольное приложение. Программа находит бумагу Лукойл, запоминает первоначальное значение середины спреда равное (bid + ask) / 2. Далее, как только значение спреда отклониться на 0.1 %, то выставляется заявка на покупку объемом 1 и ценой текущего спреда. Далее, если произойдет сделка по выставленной заявке, то программа выведет информацию по этой сделке. Вот текст программы:
// для теста выбираем бумагу Лукойл
var secCode = "LKOH";
Security lkoh = null;

// номер счета
var account = "XXX";

var waitHandle = new ManualResetEvent(false);

// создаем соединение с Quik-ом
using (var trader = new Trader(@"D:\QUIK5", "wrapper"))
{
  // подписываемся на событие появление инструментов
  trader.SecuritiesLoaded += () =>
  {
      // находим Лукойл и присваиваем ее переменной lkoh
      lkoh = trader.Securities.First(sec => sec.Code == secCode);

      Console.WriteLine("Инструмент Лукойл появился");
      waitHandle.Set();
  };

  // подписываемся на событие появления моих новых сделок
  trader.NewMyTrades += trades =>
  {
      foreach (var trade in trades)
          Console.WriteLine("Сделка {0} по цене {1} по бумаге {2} по объему {3} в {4}", trade.Id, trade.Price, trade.Security.Code, trade.Volume, trade.Time);
  };

  Console.WriteLine("Дожидаемся появления в программе инструмента Лукойл");
  waitHandle.WaitOne();

  if (lkoh != null)
  {
      // 0.1% от изменения цены
      var delta = 0.001;

      // запоминаем первоначальное значение спреда
      var firstMid = lkoh.BidAsk.Mid;
      Console.WriteLine("Первоначальное значение спреда {0}", firstMid);

      while (true)
      {
          // если спред вышел за пределы нашего диапазона
          if    (
                  ((firstMid + firstMid * delta) <= lkoh.BidAsk.Mid) ||
                  ((firstMid - firstMid * delta) >= lkoh.BidAsk.Mid)
              )
          {
              var order = new Order
              {
                  Account = account,
                  Price = lkoh.BidAsk.Mid,
                  Security = lkoh,
                  Volume = 1,
                  Direction = OrderDirections.Buy,
              };
              trader.RegisterOrder(order);
              Console.WriteLine("Заявка {0} зарегистрирована", order.Id);
              break;
          }
          else
              Console.WriteLine("Текущее значение спреда {0}", lkoh.BidAsk.Mid);
          // ждем 1 секунду
          Thread.Sleep(1000);
      }
  }
  else
      Console.WriteLine("Инструмент Лукойл не появился");

  Console.WriteLine("Заканчиваем работу. Нажмите кнопку чтобы продолжить");
  Console.Read();
}


Последнее, о чем необходимо еще упомянуть - это настройка самого Quik. Процесс несколько нудный, но, к счастью, делать нужно один раз. Самое первое, включить поддержку Внешних Транзакций. Пункт меню Торговля -> Внешние Транзакции и сделать так, как показано на рисунке:

И теперь DDE:
Инструменты


Все сделки


Заявки


Мои сделки


Важно! Колонки в таблицах должны идти так, как показаны на рисунках. Можно добавлять свои собственные колонки в конец, но никак не перемешивать с нужными. Запускать вывод через DDE тоже необходимо делать в строго определенном порядке. Сначала инструменты, затем все сделки, затем заявки, затем мои сделки.

Исходники примера, а так же сама библиотека с документацией располагается здесь:
http://www.box.net/shared/o7et6ac56x.