пятница, 23 октября 2009 г.

Ecng.Trading - новое название проекта QuikWrapper

Обновление! Рекомендую после данного сообщения прочитать это.

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

В последствии, я даже удивился, насколько это стало удобным. Например, у меня дома есть сервис с историческими данными. Эти данные я использую в программе тестирования стратегий. Так вот, очень удобно использовать Ecng.Trading для тестирования, но не очень удобно тянуть при этом Quik обертку. Зачем она, ведь я пока не торгую!? Вот и получилось, что написал я на C# с помощью библиотеки стратегию, подключил исторические данные, потестировал, подключил QuikWrapper, и уже начал торговать. И все эти шаги без переписывания кода. Я думаю, многие меня поймут, когда начинают писать прототип своей торговой системы сначала на одном языке, например на AmiBroker скрипте (или еще хуже, на Excel VBA), а затем переписывают на Delphi или .NET, так как нужна большая скорость и гибкость. Как в рекламе, зачем платить в два раза больше, если есть Ecng.Trading!

Изменение номер раз. Термины интернет-трейдинга (классы Security, Order, Trade и т.д.) перекочевали в сборку Ecng.Trading.BusinessEntities. Это полностью независимая сборка, и ее можно использовать везде, назависимо, разрабатывается ли программа под Quik или нет.

Изменение номер два. Добавились новые термины Candle, который представляет собой свечку, и Unit. Последнее собой представляет значения для хранения абсолютного или процентного значения. Поддерживаются базовые арифметически операции (+/-/*/:).

Изменение номер три. Появился интерфейс ITrader. Сам я создал только одну реализацию - QuikTrader, но если кто-то программирует под другие системы (например, Smart), пишите, попробуем добавить еще одну реализацию.

Изменение номер четыре. Базовые классы BaseTask и BaseTradeProcess для создания стратегий выделил в отдельную сборку - Ecng.Trading.Algo.

Изменение номер пять. В QuikTrader (который раньше назывался Trader) я добавил, во-первых, функционал по получению свечек, во-вторых, обработка стакана котировок, в-третьих, работа с DDE (запуск из своей программы DDE экспорт в Quik-e, которые, что важно отметить, само правильно выставляет найтроки в DDE окна Quik-а):

/// <summary>
/// Реализация интерфейса <see cref="ITrader"/>, предоставляющая шлюз взаимодействия с системой Quik.
/// </summary>
public class QuikTrader
{
/// <summary>
/// Инициализация класса <see cref="QuikTrader"/>.
/// </summary>
/// <param name="path">Путь к директории, где установлен Quik.</param>
/// <param name="ddeServer">Название DDE сервера.</param>
/// <param name="ddeWindowCaption">Заголовок окна экспорта через DDE в 
/// Quik-е. В русской версии окно имеет заголовок "Вывод через DDE сервер".</param>
/// <param name="securitiesTableCaption">Названия таблицы инструментов в Quik-е (в русской версии имеет название "Инструменты").
/// Необходимо DDE экспорту для поиска окон по заголовкам.</param>
/// <param name="tradesTableCaption">То же самое, что и securitiesTableCaption, только для всех сделок.</param>
/// <param name="ordersTableCaption">То же самое, что и securitiesTableCaption, только для заявок.</param>
/// <param name="myTradesTableCaption">То же самое, что и securitiesTableCaption, только для моих сделок.</param>
/// <param name="stockTableCaptionFormat">Формат заголовка онка с котировнками.</param>
public QuikTrader(string path, string ddeServer, string ddeWindowCaption,
string securitiesTableCaption, string tradesTableCaption,
string ordersTableCaption, string myTradesTableCaption,
string stockTableCaptionFormat);

/// <summary>
/// Запустить экспорт через DDE по всем таблицам (инструменты, сделки, заявки, мои сделки).
/// </summary>
public void StartDde();

/// <summary>
/// Запустить экспорт через DDE по таблицам, указанных параметром ddeTables.
/// </summary>
/// <example>
/// // запускаем экспорт по таблице инструментов и заявкам.
/// _trader.StartDde(DdeTables.Securities | DdeTables.Orders);
/// </example>
/// <param name="ddeTables">Таблицы, для которых необходимо запустить экспорт через DDE.</param>
public void StartDde(DdeTables ddeTables);

/// <summary>
/// Запустить для заданного инструмента экспорт через DDE стакана котировок.
/// </summary>
/// <param name="security">Инструмент, котировки которого необходимо выводить.</param>
public void StartDde(Security security);

/// <summary>
/// Остановить экспорт  DDE по всем таблицам (инструменты, сделки, заявки, мои сделки).
/// </summary>
public void StopDde();

/// <summary>
/// Остановить экспорт DDE по таблицам, указанных параметром ddeTables.
/// </summary>
/// <param name="ddeTables">Таблицы, для которых необходимо остановить экспорт через DDE.</param>
public void StopDde(DdeTables ddeTables);


/// <summary>
/// Остановить для заданного инструмента экспорт DDE стакана котировок.
/// </summary>
/// <param name="security">Инструмент, для которого необходимо остановить экспорт котировок.</param>
public void StopDde(Security security);
}


Изменение номер шесть, последнее. QuikTrader теперь поддерживает обработку по DDE стакана котировок, и получить эти котировки можно через метод GetQuotes.

Для всех нововведений приведу еще раз скриншотами настройки для DDE (новые, старые, изменившиеся):

Инструменты


Все сделки


Заявки


Мои сделки


Стакан (на примере, для LKOH)

Для закрепления того, о чем я писал выше, я создал программу-пример. На этот раз это визуальное приложение под Windows, которые представляет собой простейший привод для торговли:

пятница, 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 импорта транзакций"). В итоге получился следующий функционал:

/// <summary>
/// Основной класс, предоставляющий шлюз взаимодействия с Quik.
/// </summary>
public class Trader
{
/// <summary>
/// Инициализация класса Trader.
/// </summary>
/// <param name="path">Путь к директории, где установлен Quik.</param>
/// <param name="ddeServer">Название DDE сервера.</param>
public Trader(string path, string ddeServer);

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

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

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

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

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

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

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

/// <summary>
/// Список всех загруженных инструментов.
/// Вызывать только после того, как пришло событие <see cref="SecuritiesLoaded" />.
/// </summary>
public IEnumerable<Security> Securities;

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

/// <summary>
/// Получить все зявки, которые были зарегистрированный программой через метод <see cref="RegisterOrder" />.
/// </summary>
public IEnumerable<Order> Orders;

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

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


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

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

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

/// <summary>
/// Получить заявку по сделке.
/// </summary>
/// <param name="trade">Сделка, по которой нужно искать заявку.</param>
/// <returns>Найденная заявка.</returns>
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.