Блог им. WinterMute

Торговая система своими руками. Часть 9. Отображение результатов. Пример стратегии.

    • 18 октября 2017, 14:27
    • |
    • k100
  • Еще

     Привет всем! В предыдущем посте рассматривались два объекта, которые формируют закрытые позиции и считают статистику торговли (IClosePositionManager, IResultManager). Сегодняшняя статья будет посвящена визуализации этих данных и общей архитектуре торговой системы.

     В своё время я рассказывал про паттерн проектирования MVC, что логика должна быть отделена от визуализации, и ещё, что у каждой формы должен быть свой презентер. Также хотел отметить, что проект лучше разбивать на несколько логических модулей (библиотек классов в c#). Свой проект я разделил на: definitions – содержит базовые, ни от кого не зависящие классы, интерфейсы и описания, local – реализация интерфейсов для локального тестера, smartcom – реализация интерфейсов для коннектора, в данном случае смарткома, strategies – вынес в отдельный модуль все стратегии, UI – внешний интерфейс системы (формы и их презентеры) и т.д. В каждом таком модуле я обычно создаю ещё несколько папок – в модулеUI, например, есть папка interfaces, presenters и views.

     Сначала опишу, как отображаются результаты, а потом представлю общую картинку проекта. Итак, понадобится новый презентер, в котором считается статистика (EquityPresenter), и форма к нему (EquityView). Вот код презентера:

public class EquityPresenter
{
   private IEquityView equityView;
   private IResultManager resultManager;
   private IClosePositionManager closePositionManager;
   private IDataService dataService;

   public EquityPresenter(IEquityView equityView,
     IResultManager resultManager,
     IClosePositionManager closePositionManager,
     IDataService dataService)
   {
    this.equityView = equityView;
    this.resultManager = resultManager;
    this.closePositionManager = closePositionManager;
    this.dataService = dataService;
   }

   public void CalcAndSetEquity(string symbol,
       int strategyID)
   {
     var orders = dataService
       .GetOrders(symbol, strategyID);
     var closePositions = closePositionManager
       .ClosePositions(orders);
     var statistics = resultManager
       .Calc(closePositions);
     equityView.SetData(statistics, closePositions);
   }

   public IEquityView View => equityView;
}

     В конструкторе инициализируются необходимые в дальнейшем объекты. В методе CalcAndSetEquity, по стратегии, извлекаются из БД заявки и сделки по ним, далее, рассчитываются закрытые позиции и статистика, и, всё это передаётся в форму для отображения. Сама форма, в данном случае, это таблица со списком закрытых позиций, таблица со статистикой и собственно сам график equity. Метод формы SetData, по переданным ему данным заполняет таблицы и строит график.

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

class MainPresenter
{
   public MainPresenter(IMainView mainView,
     IStrategyFactory strategyFactory,
     IConnectGate connectGate,
     IOrderManadger orderManadger,
     IDataService dataService,
     ITickGenerator tickGenerator)
   {
     connectGate.Connected += () =>
     {
      // запустили стратегию и ждём маркет-даты
      strategyFactory
       .CreateStrategy(1)
       .Start();
        
       // подписка на событие окончания прогона
       tickGenerator.onEnd += () => {
         var orders = orderManadger
              .getOrders("GAZP", 1);
         dataService.SaveOrders(orders);
 
         // инициализация EquityPresenter’а
         var equityPresenter = ...
         equityPresenter.CalcAndSetEquity("GAZP", 1);
         equityPresenter.View.ShowForm(); // форма с equity
        } 
       tickGenerator.Start("GAZP"); // начало прогона
      }
     connectGate.Connect();
   }
}

     Итак, после запуск программы, и инициализации всех необходимых объектов, происходит подключение к серверу брокера (или имитация подключения в случае с тестером) — connectGate.Connect(). После успешного подключения, при помощи фабрики, инициализируется и запускается стратегия – strategyFactory.CreateStrategy(1).Start(). В конструкторе стратегии происходит настройка параметров и подписка на получение маркет-даты. В случае с реальной торговлей, маркет-дата начинает поступать от брокера. В случае с локальным тестером, маркет-дата вытягивается из БД и запускается процедура генерации тиковых (либо иных) сигналов — tickGenerator.Start(). Всё, робот начал торговать! Теперь осталось дождаться события окончания прогона. Инициатором такого события может быть: нажатие кнопки “стоп” на форме, какая-то временная отсечка, а в случае с локальным тестером – это просто исчерпывание локальной маркет-даты. После окончания прогона, по стратегии, получаем совершённые заявки и сделки по ним — orderManadger.getOrders(), и, сохраняем всё это в базу — dataService.SaveOrders(). Далее, инициализируется EquityPresenter, и, происходит подсчёт статистики – equityPresenter.CalcAndSetEquity(), теперь, осталось только вывести результаты на экран – equityPresenter.View.ShowForm().

     И, напоследок, небольшой пример стратегии. Описание самих стратегий выходит за рамки данной серии статей, поэтому приведу пример реализации самой простой, сливной стратегии, лишь для того, чтобы продемонстрировать, что весь механизм работает и какой строится итоговый график.

     Суть стратегии – пробой предыдущего часового бара. На тиковых данных считается максимум и минимум предыдущего часа, если текущая цена пробила максимум – покупка, если пробила минимум — продажа. Выход из позиции: либо по стопу в 1% от цены, либо по тейку в 3% от цены. Комисы 0.035% на круг, проскальзывание не учитывается. Гоняем один лот. Ниже представлен код, реализующий эту стратегию:

public class SimpleStrategy : IStrategy
{
   private IMarketDataGate marketDataGate;
   private IOrderManager orderManager;
   private int strategyID = 1;
   private string symbol = "GAZP";
   private double prewHight = 0;
   private double prewLow = 999;
   private double curHight = 0;
   private double curLow = 999;
   private int amount = 10;
   private int positionDirection = 0;
   private double take;
   private double stop;
   private DateTime updateDate;       

   public SimpleStrategy (
     IMarketDataGate marketDataGate,
     IOrderManager orderManager)
   {
     this.marketDataGate = marketDataGate;
     this.orderManager = orderManager;
   }       

   public void Start()
   {
     marketDataGate.AddTick += AddTickHandler;
     marketDataGate.ListenTicks(symbol);
   }

   private void AddTickHandler(object o, 
     AddTickEventArgs e)
   {
     if (e.DateTime > updateDate)
     {
       prewHight = curHight;
       prewLow = curLow;
       curHight = e.Price;
       curLow = e.Price;
       updateDate = e.DateTime.AddMinutes(60);
     }

     if (e.Price > curHight) curHight = e.Price;
     else if (e.Price < curLow) curLow = e.Price;

     if (positionDirection == 0 && e.Price > prewHight)
     {
       orderManager.PlaceOrder(symbol, strategyID, 
         OrderAction.Buy, OrderType.Market, amount);
       positionDirection = 1;
       take = e.Price + e.Price * 0.03;
       stop = e.Price - e.Price * 0.01;
     }
     else if (positionDirection == 0 && e.Price < prewLow)
     {
       orderManager.PlaceOrder(symbol, strategyID, 
         OrderAction.Sell, OrderType.Market, amount);
       positionDirection = -1;
       take = e.Price - e.Price * 0.03;
       stop = e.Price + e.Price * 0.01;
     }
     else if (positionDirection == 1 
       && (e.Price > take || e.Price < stop))
     {
       orderManager.PlaceOrder(symbol, strategyID,
         OrderAction.Sell, OrderType.Market, amount);
       positionDirection = 0;
     }
     else if (positionDirection == -1 
       && (e.Price < take || e.Price > stop))
     {
       orderManager.PlaceOrder(symbol, strategyID,
         OrderAction.Buy, OrderType.Market, amount);
       positionDirection = 0;
      }
   }
}

     В методе Start происходит подписка на тиковые данные, далее, стратегия ожидает их прихода. На каждый тик выполняется сам алгоритм бота: если прошёл очередной час – устанавливаются новые границы канала (high и low предыдущего часового бара). Если позиция не открыта (positionDirection == 0) и произошёл пробой (либо вверх, либо вниз), то соответствующая позиция открывается. Если позиция уже открыта и цена достигла значения take или stop – позиция закрывается. Код простейший, и много чего не учитывает, например, перенос через ночь, лимит времени в позиции, и, многие другие аспекты торговли. Но думаю, общая картинка понятна. Тут используются описанные ранее IMarketDataGate, для получения маркет-даты и, IOrderManager, для выставления и протоколирования заявок. Тестирование проводилось по акциям Газпрома с мая по июнь 2017г. И вот, что наторговал бот-сливала:
Торговая система своими руками. Часть 9. Отображение результатов. Пример стратегии.

     Даже немного в плюс!))

     В следующей статье я расскажу про логирование, что такое IoC контейнер, и как, при помощи одной лишь переменной, менять режимы работы – тестовый или торговый.

254 | ★14
7 комментариев
Спасибо! Продолжай)
avatar
А есть возможность запросить какое-то количество исторических тиковых данных и послать их в стратегию в качестве инициализации?
avatar
tranquility, обычно коннекторы поддерживают механизм получения ретроспективных данных, в 4 части данной серии топиков, я описывал способ получения таких данных через асинхронные методы.
avatar
круто блин
avatar
Спасибо за статьи!

Вы сказали, что используете SmartCom. А к чему он подсоединяется (торговый терминал, напрямую к брокеру, бирже)?
 Накопал в инете, так, работая со SmartCom, вы  привязаны к айтиинвест?
Евгений Гуревич, Вы правы, привязан. Но! Т.к. это собственный протокол брокера, то это даёт определённые скоростные и инфраструктурные преимущества. Также, я старался разрабатывать систему так, чтобы, по возможности, не сильно зависеть от брокера и его API — т.е. если потребуется перейти, например на транзак, то это потребует минимальных переделок — код стратегий, работа с БД, локальное протоколирование заявок/сделок — не поменяются.
Хотя оговорюсь, что универсальность тут не к чему — универсальные и гибкие решения дороги в обслуживании и медленные. Всё равно будет какая-то притирка к API брокера, я лишь хотел выделить её в отдельный модуль, сделать просто ещё одним аспектом системы.

avatar

Читайте на SMART-LAB:
Фото
Долгосрочное инвестирование умерло. В этот раз - без "но". Хороших новостей не будет
Увеличение капитала посредством инвестирования в доли компаний всегда основывалось на двух тезисах (1) компания сможет на длительном...
Фото
Как на самом деле используют ИИ в алготрейдинге
Если первая часть моего репортажа по конференции алготрейдеров в Москве была об инфраструктуре, то вторая часть будет про искусственный...
«Профи» из группы Займер окупил первый приобретенный портфель
Делимся новостями коллекторского агентства из группы Займер. КА «Профи» вышло на точку окупаемости по первому приобретенному портфелю. ⚡️ Для...
Фото
Ростелеком. МСФО за Q4 2025г. Всё неплохо… но всё равно печально…
Компания Ростелеком опубликовала финансовые результаты за 4 квартал 2025г.: 👉Выручка — 270,5 млрд руб. (+15,6% г/г) 👉Операционные...

теги блога k100

....все тэги



UPDONW
Новый дизайн