Добрый день. В предыдущем посте были описаны базовые компоненты – классы обёртки над API брокера. Не хотелось нагружать их дополнительной логикой, поэтому оставим их как есть, и перейдём к чуть более сложному объекту. На сцене появляется IOrderManager, который отвечает за заявки и сделки по ним.
interface IOrderManager { List<Order> GetOrders(string symbol, int strategyID); void PlaceOrder(string symbol, int strategyID, OrderAction action, OrderType type, double price, double amount, double stopPrice); }
Всего два метода – выставить заявку и получить их список. Но, у реализации IOrderManager’а непростая задача – надо не просто выставлять заявки, но также хранить какая стратегия это сделала и какие прошли сделки. Получается, у OrderManager’а есть некое состояние – список заявок/сделок, поэтому этот объект относится больше к модели, чем к сервисному слою программы. Перед этим я описывал IPortfolioGate – класс-обёртка для работы с портфелем, вот у него нет состояния, он просто транслирует вызов методов внешней COM библиотеки, а вот OrderManager это некий дополнительный уровень над всем этим – у него появляются «знания» о предметной области, и именно он используется в классах стратегий.
Также, появляются две сущности – заявка (Order) и сделка (Trade). Класс Order имеет список сделок прошедших по данной заявке.
class Order { public string Symbol { get; set; } public OrderAction Action { get; set; } public double Price { get; set; } … public List<Trade> Trades { get; set; } }
Кстати, выражение вида List<Т> – это generic или обобщение: объявляется коллекция (List) и указывается её тип. Это упрощает работу с данными и гарантирует типовую безопасность, ведь обращение к элементам коллекции идёт в терминах T (Trade в данном случае). Теперь подробно о внутренней реализации OrderManager’а.
class OrderManager : IOrderManager { private int cookieSequence = 1; private string portfolio = "xx-xxxxx-xxx"; private IPortfolioGate gate; // обёртка над смарткомом или заглушка private List<Order> tmpOrders = new List<Order>(); private List<Trade> tmpTrades = new List<Trade>(); public OrderManager(IPortfolioGate gate) { this.gate = gate; this.gate.UpdateOrder += UpdateOrderEventHandler; this.gate.AddTrade += AddTradeEventHandler; } public void PlaceOrder(string symbol, int strategyID, OrderAction action, OrderType type, double price, double amount, double stopPrice) { var cookie = ConcatStrategyIDAndSequence(strategyID, cookieSequence++); gate.PlaceOrder(portfolio, symbol, action, type, price, amount, stopPrice, cookie); } private void UpdateOrderEventHandler(object o, UpdateOrderEventArgs e) { if (e.State == OrderState.Filled) { var newOrder = new Order(e.Portfolio, e.Symbol, e.DateTime, e.Action, e.Type, e.Price, e.Amount, e.StopPrice, e.OrderId, e.OrderNo, GetStrategyIDFromCookie(e.Cookie), e.Cookie) tmpOrders.Add(newOrder); } } private void AddTradeEventHandler(object o, AddTradeEventArgs e) { var newTrade = new Trade(e.DateTime, e.Price, e.Amount, e.OrderNo, e.TradeNo); tmpTrades.Add(newTrade); }
Итак, суть процесса такова: Сперва, метод PlaceOrder выставляет заявку. По сути, тут вызывается метод PlaceOrder из IPortfolioGate, но OrderManager делает ещё кое-что – генерирует кук (потом этот кук придёт в событии изменения статуса заявки). Кук – это пометка для заявки перед её выставлением, благодаря ему, потом, можно понять, какая конкретно из заявок удовлетворена. Это не всё – надо как-то различать, какая стратегия отправила заявку. Для этого, в кук кодируется ID стратегии – я написал метод ConcatStrategyIDAndSequence, который генерирует число вида: [id стратегии формата XXX, начиная со 101][последовательный номер кука]. После того, как заявка выставлена, надо ловить событие изменения её статуса – это происходит в событии UpdateOrderEventHandler. Тут, мы получаем Id заявки от брокера и кук, из которого извлекается id стратегии (GetStrategyIDFromCookie). Если заявка удовлетворена, мы вставляем все эти данные в коллекцию заявок. После этого, надо ловить событие регистрации сделки (AddTradeEventHandler) – тут, в коллекцию сделок, вставляется очередная сделка.
На данный момент, заявки и сделки – это две не связанных между собой коллекции. Я хотел минимизировать нагрузку в момент выставления заявки и регистрации сделки. Полная картинка получается в момент вызова метода GetOrders – получить все текущие заявки/сделки – тут они и связываются. Связать эти две коллекции возможно по Id заявки от брокера – это поле есть и у заявки и у сделки по ней прошедшей. Одна из возможных реализаций GetOrders такова:
public List<Order> GetOrders(string symbol, int strategyID) { return tmpOrders.Where(o => (o.Symbol == symbol) && (o.StrategyID == strategyID)) .Join(tmpTrades, o => o.BrokerOrderNo, t => t.BrokerOrderNo, (o, t) => o.AddTrade(t)) .Distinct() .ToList(); }
Выбираем необходимые заявки, соединяемся со сделками по id заявки, и в качестве результата возвращаем o.AddTrade(t) – метод класса Order, который добавляет сделку к заявке и возвращает саму эту заявку (такой приём, чтобы два действия свести к одному). В конце стоит Distinct (выбросить одинаковые заявки), т.к. на одну заявку может быть несколько сделок, и при соединении (join) часть задвоится. Всё это сделано при помощи лямбда выражений и linq интерфейса. Например, в метод Where передаётся анонимная функция, которая будет вызвана для каждого элемента коллекции, и, в итоговую коллекцию попадут те элементы, по которым функция примет истинное (true) значение (в данном случае фильтрация по символу и стратегии).
В итоге, общая картинка такова: стратегии, чтобы выставлять заявки, используют IOrderManager. Стратегия не знает ни про куки, ни про смартком, ни про то, как протоколируются заявки. Класс, реализующий IOrderManager, берёт на себя ответственность за генерацию кука, учёт Id стратегий, и связыванию заявок и сделок. OrderManager использует IPortfolioGate, который в свою очередь является обёрткой над смарткомом, либо заглушкой. Впоследствии, другой класс, который считает закрытые позиции, получит список всех сделок (метод GetOrders из IOrderManager). Но об этом позже.
Уже пишу под Tranzaq connector
Спасибо, интересная серия статей. Тоже подумываю написать аналогичную систему для себя.
А почему метод
PlaceOrder
не принимает сразу объект классаOrder
? Это только из-за необходимости создаватьcookie
?