Блог им. WinterMute
Добрый день. В предыдущих частях я описывал, как на C# сделал собственный тестер, применяя объектно-ориентированный подход, рассказывал про интерфейсы, про их реализации, и, рассказывал про работу с БД. На данный момент осталось совсем немного. В этом топике я опишу вариант расчёта результатов работы стратегии.
Чтобы не запутаться, даже не читая предыдущие топики, поясню, что есть и к чему надо придти. Есть стратегии – это некий объект программы, который выставляет заявки на основе получаемой маркет-даты. Заявки (Order) регистрируются системой. Также, регистрируются сделки прошедшие по заявке (каждая заявка имеет список сделок — List<Trades> trades). После прогона стратегии, все заявки и сделки сохраняются в БД, и после, их можно извлечь и посчитать по ним статистику работы стратегии. По сути, эта статистика состоит из двух аспектов: сами закрытые позиции и оценка эффективности на их основе. Начнём с первого. Вот интерфейс, который принимает заявки со сделками, и, выдаёт, собственно, список закрытых позиций:
interface IClosePositionManager { List<ClosePosition> ClosePositions (List<Order> orders); }
Класс ClosePosition, имеет следующие поля: id закрытой позиции, объём, сделка инициатор, закрывающая сделка, нарастающий итог, профит/убыток, комиссия, время в позиции и т.д.
Как реализовать этот интерфейс? Сделки проходят по разным ценам, и чтобы пазл сложился нужно последовательно выполнить расчёт – по сути, надо свести разнонаправленные сделки по своим ценам. Для расчёта использую очереди и рекурсию. Беру все сделки по символу и ранжирую их по порядку (по Id сделки). Далее, начинаю считать. Беру первую сделку (объём, цена) и помещаю её в очередь, беру следующую, если направление то же, то и её помещаю в очередь. Если направление противоположное, то формируется закрытая позиция. Тут важен объём. В простом случае, объёмы текущей сделки и сделки из очереди равны – они схлопнулись и текущая сделка и сделка из очереди ушли, все по своим ценам. Если объём текущей меньше чем из очереди, то текущая уходит, а объём из очереди кратно уменьшается. А если объём текущей больше чем общём из очереди – то текущая, как бы проваливается ниже, кратно поглощая сделки из очереди. Код, если кому надо – приведу. Соответственно по каждой закрытой позиции есть цена покупки и продажи, на основе которых считается профит/убыток.
Теперь вторая составляющая статистики – оценка эффективности работы стратегии на основе её закрытых позиций:
interface IResultManager { IndexOfEffectiveness Calc(List<ClosePosition> closePositions); }
Класс IndexOfEffectiveness включает в себя набор стандартных (да и вообще, каких угодно) показателей: общая доходность, DrawDown, PF, RF, средняя сделка, Шарп и т.д.:
class IndexOfEffectiveness { [DisplayName("Общая доходность")] public BuySellResult<double> Yield { get; set; } [DisplayName("Максимальный DrawDown")] public BuySellResult<double> MDD { get; set; } [DisplayName("Profit Factor")] public BuySellResult<double> PF { get; set; } [DisplayName("Recovery Factor")] public BuySellResult<double> RF { get; set; } … и т.д. }
Тут перечислены все необходимые статистические показатели. Важно отметить, что в качестве возвращаемого типа каждого показателя используется обобщённый класс-обёртка BuySellResult, состоящий из трёх полей – покупки, продажи, всё вместе. Это нужно чтобы разделить, например, Максимальный DrawDown по покупкам и продажам.
Что касается самой реализации интерфейса IResultManager (метод Calc), то тут, нудно и последовательно, выполняется расчет всех показателей:
public IndexOfEffectiveness Calc(List<ClosePosition> closePositions) { var buyPositions = closePositions .Where(cp => cp.Init.Order .Action == OrderAction.Buy); var sellPositions = closePositions .Where(cp => cp.Init.Order .Action == OrderAction.Sell); var tradesCount = new BuySellResult<int>() { Buy = buyPositions.Count(), Sell = sellPositions.Count(), All = closePositions.Count() }; var winPositions = new BuySellResult<IEnumerable<ClosePosition>>() { Buy = buyPositions.Where(cp => Math.Sign(cp.ClearProfit) == 1), Sell = sellPositions.Where(cp => Math.Sign(cp.ClearProfit) == 1), All = closePositions.Where(cp => Math.Sign(cp.ClearProfit) == 1) }; var winCount = new BuySellResult<int>() { Buy = winPositions.Buy.Any() ? winPositions.Buy.Count() : 0, Sell = winPositions.Sell.Any() ? winPositions.Sell.Count() : 0, All = winPositions.All.Any() ? winPositions.All.Count() : 0 }; ... и т.д. }
И всё остальное в том же духе. В конце создаётся класс IndexOfEffectiveness и ему передаются все рассчитанные выше показатели.
Кстати в классе IndexOfEffectiveness используются атрибуты (в частности DisplayName из стандартного пространства имён System.ComponentModel). Атрибуты (или аннотации в Java) делают программирование более декларативным, по сути, это простейший вид рефлексии. Например, в DisplayName я указываю, как должна называться соответствующая колонка в таблице отображающий результаты. При инициализации таблиц в visual studio, этот атрибут подхватывается. Также, например, атрибуты используют для задания ограничений, которые автоматически проверяются в определённый момент. Можно реализовать собственные атрибуты с оригинальным поведением.
После прогона стратегии, рассчитываются закрытые позиции и показатели эффективности. Эти данные передаются в форму для визуализации результата. Про саму визуализацию я напишу в следующем посте.
Вообще как вамскорость на смарт ком?
Изучаю C#. Уже многое понятно и по сути написать простого бота знаний хватит, а вот терминал, работу с БД и тестер пока нет!
По с# — если будут вопросы, задавайте, я с удовольствием проконсультирую)