Решил поддержать хорошее начало по части написания статей на полезные темы. Попробую рассмотреть логику создания стратегий. Для многих начинающих я думаю это будет полезным подспорьем. Ну и возможно бывалые извлекут себе что-то полезное.
Предположим, что наши стратегии работают на таймфрэйме от 5 минут, нам не требуется работа с тиками. Отсюда требования к скорости работы алгоритмов будет достаточно лояльные. При описанных допущениях наиболее логичным будет разделять алгоритмы на части. Каждая часть будет выполнять заранее определенную операцию. Собирая вместе части, мы будем получать целое.
Рассмотрим такой момент как защита сделки. Когда стратегия входит в сделку и совершает трейд, часто требуется защитить сделку стопами, профитами и может быть чем-нибудь еще. Логично будет разделить разные элементы защиты и создать на каждый отдельную защитную стратегию. В базовой поставке S# есть уже несколько готовых защитных стратегий: StopLoss, TakeProfit и так далее. Все они базируются на стратегии котирования, что не всегда бывает необходимо. Например, если ваш брокер имеет ограничение на количество транзакций в секунду, то запустив несколько одновременных стратегий котирования можно попасть в неудобную ситуацию превышения лимита транзакций. Поэтому для себя лично я реализовал свои защитные стратегии, базированные НЕ на котировании, а на простом рыночном входе. Для удобной работы с пачкой защитных стратегий, была реализована корзина защитных стратегий. Перейдем собственно к делу.
Для реализации корзины стратегий, нам понадобится универсальный интерфейс защитной стратегии.
Code
/// <summary>
/// Защитная стратегия, которая срабатывает при определенных условиях. Предназначена для защиты сделок.
/// </summary>
public interface IProtectionStrategy
{
/// <summary>
/// Событие вызывается сразу же как только защитная стратегия активировалась
/// и начала закрывать позицию.
/// </summary>
event Action Activated;
/// <summary>
/// Защищаемая сделка.
/// </summary>
MyTrade ProtectedTrade { get; }
/// <summary>
/// Текущее состояние стратегии, активирована она лии нет.
/// </summary>
bool IsActivated { get; }
}
Далее на базе интерфейса мы уже можем реализовать корзину стратегий. Каждая из защитных стратегий корзины, будет защищать одну конкретную сделку. И вся корзина будет защищать эту сделку. Смысл корзины в том, что когда активировалась одна из защитных стратегий, остальные должны быть остановлены. Защитная стратегия просто совершает сделку обратную защищаемой сделке, отсюда необходимо избегать ситуации, когда сработают несколько защитных стратегий. Как только активировалась защитная стратегия, корзина тоже переходит в Активное состояние. В принципе корзина сама реализует интерфейс защитной стратегии и ведет себя аналогичным образом. После завершения активной защитной стратегии, корзина тоже завершает свою работу. Ниже пример реализации:
Code
/// <summary>
/// <para>
/// Стратегия, которая реализует корзину защитных стратегий. В стратегию объединяются несколько защитных стратегий защищающих
/// одну и ту же сделку. Как только будет активирована защита одной из нескольких защитных стратегий, остальные будут остановлены
/// и после завершения активировавшейся защитной стратегии, вся баскет стратегия будет тоже остановлена.
/// В момент активации одной из защитных стратегий, баскет стратегия переходит в активированное состояние и свойство <see cref="FirstActivated"/>
/// указывает на активировавшуются защитную стратегию.
/// </para>
/// <para>
/// Данная стратегия нужна если есть комбинация различных взаимоисключающих защитных техник и при срабатывании одной из них, нужно блокировать
/// работу остальных. Например несколько видов стопов, или стоплосс-тейпрофит итд.
/// </para>
/// </summary>
public class BasketProtectionStrategy : Strategy, IProtectionStrategy
{
/// <summary>
/// Объект для синхронизации правил активации дочерних защитных событий. Обеспечивает срабатывание одной из дочерних стратегий
/// после которого все остальные железно будут остановлены.
/// </summary>
private object _syncToken = new object();
/// <summary>
/// Защищаемая сделка.
/// </summary>
public MyTrade ProtectedTrade { get; protected set; }
/// <summary>
/// Текущее состояние стратегии, активирована она лии нет.
/// </summary>
public bool IsActivated { get; protected set; }
/// <summary>
/// Стратегия которая активировалась первой среди защитных.
/// </summary>
public IProtectionStrategy FirstActivated { get; protected set; }
/// <summary>
/// Событие вызывается сразу же как только защитная стратегия активировалась
/// и начала закрывать позицию.
/// </summary>
public event Action Activated;
public BasketProtectionStrategy()
{
ProtectedTrade = null;
IsActivated = false;
ChildStrategies.Adding += ChildStrategiesOnAdding;
}
/// <summary>
/// Метод вызывается тогда, когда вызвался метод <see cref="M:StockSharp.Algo.Strategies.Strategy.Start"/>, и состояние <see cref="P:StockSharp.Algo.Strategies.Strategy.ProcessState"/> перешло в значение <see cref="F:StockSharp.Algo.Strategies.ProcessStates.Started"/>.
/// </summary>
protected override void OnStarting()
{
if (ChildStrategies.Count == 0)
throw new InvalidOperationException("Дочерние стратегии отсутствуют, невозможно запустить комплесную защитную стратегию.");
base.OnStarting();
}
protected override void OnStopping()
{
Debug.WriteLine("OnStopping: Число правил {0}".Put(Rules.Count));
this.AddInfoLog("Очищая список правил стратегии.");
Rules.Clear();
this.AddInfoLog("Останавливаю все дочерние стратегии.");
ChildStrategies.ToArray().ForEach(s => s.Stop());
base.OnStopping();
}
/// <summary>
/// Метод вызывается при добавлении дочерней стратегии. Проверяет параметры стратегии и вешается на событие <see cref="IProtectionStrategy.Activated"/>.
/// </summary>
/// <param name="strategy">Добавляемая защитная стратегия. Должна соответствовать интерфейсу <see cref="IProtectionStrategy"/></param>
private void ChildStrategiesOnAdding(Strategy strategy)
{
// проверяем чтобы защитная стратегия была, а не какая-то другая.
if (!(strategy is IProtectionStrategy))
throw new InvalidOperationException("Дочерней может быть только защитная стратегия реализующая IProtectionStrategy.");
var protectionStrategy = strategy as IProtectionStrategy;
// проверяем чтобы защитные стратегии были для одной сделки. Если защищаемая сделка еще не определена, берем из добавляемой стратегии.
ProtectedTrade = ProtectedTrade ?? protectionStrategy.ProtectedTrade;
if (ProtectedTrade != protectionStrategy.ProtectedTrade)
throw new InvalidOperationException("Добавляемые защитные стратегии должны защищать одну и ту же сделку.");
// как только активировалась защита одной из дочерних стратегий, сразу же активируем защиту комплексной стратегии.
// Все правила будут синхронизированы через общий объект, так что вызываться смогут только по очереди. После вызова первого мы удалим все остальные.
this
.When(protectionStrategy.ProtectionActivated())
.Do(s => Activate(s) )
.Once()
.Sync(_syncToken);
}
/// <summary>
/// Активирует защиту сделки, то есть начинает процедуру защиты.
/// Если защита уже активирована, то ничего не делает. Состояние защиты проверяется
/// через свойство <see cref="IsActivated"/>. В случае активации генерирует событие <see cref="Activated"/>.
/// Нужно самому обеспечивать потокобезопасность вызова метода. При одновременном вызове может быть коллизия.
/// </summary>
/// <param name="strategy">Стратегия которая активировалась и вызвала активацию комплексной защитной стратегии. </param>
protected virtual void Activate(IProtectionStrategy strategy)
{
// защита от двойного входа в состояние защиты.
if (IsActivated) return;
IsActivated = true;
FirstActivated = strategy;
this.AddInfoLog("Активировалась одна из дочерних стратегий защиты, завершаем остальные стратегии защиты.");
RaiseActivatedEvent();
// очищаем все правила стратегии и останавливаем все дочерние кроме активной. Когда активная тоже остановится, защитная стратегия тоже остановится.
Rules.Clear();
// после остановки активной дочрней стратегии мы остановим и комплексную защитную.
this
.When((strategy as Strategy).Stopped())
.Do(Stop);
StopNotActiveChilds();
}
/// <summary>
/// Останавливает все дочерние защитные стратегии для сделки, кроме той которая активировалась.
/// </summary>
private void StopNotActiveChilds()
{
var firstActivated = FirstActivated as Strategy;
var stoppingStrategies = ChildStrategies.Where(s => s != firstActivated).ToArray();
stoppingStrategies.ForEach(s => s.Stop());
}
/// <summary>
/// Сгенерировать событие об активации защиты.
/// </summary>
protected virtual void RaiseActivatedEvent()
{
Activated.SafeInvoke();
}
protected override void DisposeManaged()
{
ChildStrategies.Adding -= ChildStrategiesOnAdding;
base.DisposeManaged();
}
}
Далее реализуем защитную стратегию которую будем добавлять в корзину. Первое что хочется показать, это реализацию стратегии, которая выставляет физический стоп и мониторит его состояние. Как только стоп активируется, стратегия становится активной. Если стратегию остановить, стоп заявка снимается. Я выбрал эту стратегию, из-за определенных нюансов в реализации, которые многим помогут.
Code
/// <summary>
/// Данная стратегия используется для выставления реального стопа и слежения за его состоянием.
/// Когда стоп начнет исполняться, стратегия активирует событие <see cref="Activated"/> и переходит в активированное состояние.
/// </summary>
public class RealStopLossStrategy : Strategy, IProtectionStrategy
{
private Unit _protectionLevel;
private Unit _slippage;
private Order _stopOrder;
private object _globalLock = new object();
/// <summary>
/// Защищаемая сделка.
/// </summary>
public MyTrade ProtectedTrade { get; protected set; }
/// <summary>
/// Текущее состояние стратегии, активирована она лии нет.
/// </summary>
public bool IsActivated { get; protected set; }
/// <summary>
/// Событие вызывается сразу же как только защитная стратегия активировалась
/// и начала закрывать позицию.
/// </summary>
public event Action Activated;
/// <summary>
/// Создает стратегию защиты сделки на основе скользящей средней.
/// </summary>
/// <param name="protectedTrade">Защищаемая сделка.</param>
/// <param name="protectionLevel">Уровень на который стоп отстоит от цены сделки. Может быть фикс, или процент. </param>
/// <param name="slippage">Проскальзывание, которое будет выставлено в стоп ордере. </param>
/// <exception cref="ArgumentNullException">Если не заданы аргументы.</exception>
/// <exception cref="ArgumentException">Если скользящая НЕ сформирована.</exception>
public RealStopLossStrategy(MyTrade protectedTrade, Unit protectionLevel, Unit slippage)
{
if (protectedTrade == null)
throw new ArgumentNullException("protectedTrade");
if (protectionLevel == null)
throw new ArgumentNullException("protectionLevel");
if (slippage == null)
throw new ArgumentNullException("slippage");
/*
if ( (protectionLevel.Type != UnitTypes.Percent) && (protectionLevel.Type != UnitTypes.Absolute) )
throw new ArgumentException("Уровень стопа можно задавать либо в абсолютных значениях либо в процентах.", "protectionLevel");
if ((slippage.Type != UnitTypes.Percent) && (slippage.Type != UnitTypes.Absolute))
throw new ArgumentException("Уровень проскальзывания можно задавать либо в абсолютных значениях либо в процентах.", "slippage");
*/
ProtectedTrade = protectedTrade;
// это нужно чтобы могли формироваться заявки через хелпер методы. Им нужен будет объем.
this.Volume = ProtectedTrade.Trade.Volume;
_protectionLevel = protectionLevel;
_slippage = slippage;
}
protected override void OnStarting()
{
base.OnStarting();
this.AddWarningLog("Начинаю защищать сделку Id: {0} по ордеру Id: {1}".Put(ProtectedTrade.Trade.Id, ProtectedTrade.Order.Id));
// когда позиция стратегии изменилась, значит защита активировалась и стоп сработал войдя в сделку.
this
.When(this.PositionChanged())
.Do(s =>
{
Activate();
TryStop();
});
Begin();
}
protected override void OnStopping()
{
this.AddInfoLog("Очищаю список правил стратегии.");
Rules.Clear();
this.AddInfoLog("Останавливаю все дочерние стратегии.");
ChildStrategies.SyncDo(c => c.ToArray().ForEach(strat => strat.Stop()));
base.OnStopping();
}
/// <summary>
/// Метод вызывается тогда, когда состояние процесса <see cref="P:StockSharp.Algo.Strategies.Strategy.ProcessState"/> перешло в значение <see cref="F:StockSharp.Algo.Strategies.ProcessStates.Stopped"/>.
/// </summary>
protected override void OnStopped()
{
var currVol = this.PositionManager.Position.Abs();
var baseVol = ProtectedTrade.Trade.Volume;
var restVol = baseVol - currVol;
if (currVol != baseVol)
this.AddWarningLog("Стратегия RealStopLossStrategy завершила свою работу с оставшимся защищаемым объемом {0}. Исходный объем {1}".Put(restVol, baseVol));
else
this.AddWarningLog("Стратегия RealStopLossStrategy завершила свою работу с полностью исполненным защищаемым объемом. Исходный объем {0}".Put(baseVol));
base.OnStopped();
}
public override void Stop()
{
// если есть активные ордера, то не завершаем работу а пробуем сначала отменить эти ордера.
// отменяем все оставшиеся активные ордера в стратегии. Например стратегия была частью баскет стратегии и была остановлена, останутся стопзаявки.
// нужно удалить все зависшие стоп заявки стратегии.
if (this.HaveActiveStopOrders())
{
this.AddInfoLog("Стратегия имеет активные ордера, пробуем отменить их.");
this.GuarantyCancelOrders(new [] {_stopOrder});
this
.When(_stopOrder.Canceled())
.Do(o =>
{
this.AddInfoLog("Активные ордера отменены, останавливаем стратегию.");
Stop();
});
return;
}
base.Stop();
}
private void Begin()
{
Order order = null;
var direction = ProtectedTrade.Order.Direction.Invert();
var price = ProtectedTrade.Trade.Price;
// сформируем ордер в зависимости от направления стратегии
if (direction == OrderDirections.Buy)
{
var stopPrice = _protectionLevel.Type == UnitTypes.Limit
? _protectionLevel.Value
: price + _protectionLevel;
var slippage = _slippage.Type == UnitTypes.Limit ? _slippage : (price + _slippage) - price;
order = this.CreateBuyStopOrder((double)slippage, (double)stopPrice);
}
if (direction == OrderDirections.Sell)
{
var stopPrice = _protectionLevel.Type == UnitTypes.Limit
? _protectionLevel.Value
: price - _protectionLevel;
var slippage = _slippage.Type == UnitTypes.Limit ? _slippage : (price + _slippage) - price;
order = this.CreateSellStopOrder((double) slippage, (double)stopPrice);
}
// выставляем ордер
SetOrder(order);
}
/// <summary>
/// Выставляет ордер и все правила на ордер вешает.
/// </summary>
/// <param name="order"></param>
private void SetOrder(Order order)
{
#region Заводим стратегию выставления заяки для гарантии того что заявка поставилась.
// сия стратегия нам гарантированно ордер поставит, или выдаст ошибку если поставить не получится.
var setOrderStrategy = new GuarantyRegisterOrderStrategy(order);
SuspendRules();
var errorRule = StrategyRuleHelper.Error(setOrderStrategy);
var stoppedRule = setOrderStrategy.Stopped();
var syncToken = new object();
// Если зарегать ордер не удается мы завершаем работу стратегии с ошибкой.
this
.When(errorRule)
.Do(e =>
{
Rules.Remove(stoppedRule);
var msg = "При регистрации ордера TransId: {0} произошла фатальная ошибка {1}.".Put(setOrderStrategy.Order.TransactionId, e.Message);
FatalFinish(msg);
})
.Once()
.Sync(syncToken);
// Когда стратегия остановилась перед ошибкой, она значит завершилась успешно.
this
.When(stoppedRule)
.Do(s =>
{
Rules.Remove(errorRule);
this.AddInfoLog("Ордер успешно зарегистрирован TransId: {0} State: {1}", setOrderStrategy.Order.TransactionId, setOrderStrategy.Order.State);
SetOrderRules(setOrderStrategy.Order);
})
.Once()
.Sync(syncToken);
ResumeRules();
// запуск стратегии регистрации ордера.
ChildStrategies.Add(setOrderStrategy);
#endregion
}
private void SetOrderRules(Order order)
{
if (order == null)
throw new ArgumentNullException("order", "Стоп ордер не задан для стратегии");
_stopOrder = order;
// когда состояние стоп ордера изменилось, и он стал активен, значит сработали стоп условия и защита активировалась.
this
.When(_stopOrder.Changed())
.Do(o =>
{
if (o.State != OrderStates.Active) return;
Activate();
});
}
/// <summary>
/// Активирует защиту сделки, то есть начинает процедуру защиты.
/// Если защита уже активирована, то ничего не делает. Состояние защиты проверяется
/// через свойство <see cref="IsActivated"/>. В случае активации генерирует событие <see cref="Activated"/>
/// </summary>
protected virtual void Activate()
{
// защита от двойного входа в состояние защиты.
lock (_globalLock)
{
if (IsActivated) return;
IsActivated = true;
}
this.AddInfoLog("Стоп ордер Id: {0} сработал. Защита активировалась.".Put(_stopOrder.Id));
RaiseActivatedEvent();
}
/// <summary>
/// Сгенерировать событие об активации защиты.
/// </summary>
protected virtual void RaiseActivatedEvent()
{
Activated.SafeInvoke();
}
/// <summary>
/// Метод проверяет текущую позицию стратегии, если она стала равна объему защищаемой сделки, значит стоп ордер
/// исполнился, стратегия совершила контр сделку и может быть остановлена.
/// </summary>
/// <returns></returns>
protected virtual bool NeedStop()
{
var absPos = this.PositionManager.Position.Abs();
var vol = ProtectedTrade.Trade.Volume;
return absPos == vol;
}
protected virtual bool NeedActivate()
{
throw new NotImplementedException("NeedActivate");
}
private void TryStop()
{
if (NeedStop()) Stop();
}
protected virtual void Finish()
{
Stop();
}
protected virtual void FatalFinish(string message)
{
OnError(new Exception(message));
Stop();
}
}
Как видно, в стратегии используется подстратегии для регистрации ордеров и для отмены ордеров. Это укладывается в выбранную парадигму реализации операций в виде отдельных частей. При регистрации ордера, может произойти ошибка или еще какой неприятный момент. Заявку надо будет переподать и отследить ее регистрацию. Все это реализуемо в виде отдельной стратегии. Так же можно поступить с отменой заявок, что мной и сделано. Реализацию этих подстратегий не выкладываю, оставим как домашнее задание .
Теперь собственно пример использования всего этого добра . Смотрим ниже.
Code
var fixStopStrategy = new RealStopLossStrategy(trade, FixedStop, 2.Percents());
var protectStrategy = new BasketProtectionStrategy();
protectStrategy.ChildStrategies.Add(fixStopStrategy);
ChildStrategies.Add(protectStrategy);