Стратегия является портом MetaTrader 4 советника TrainYourself-V1_1-1 на платформу StockSharp. В оригинале построение канала и открытия/закрытия позиций выполнялись через элементы на графике. В версии для StockSharp канал рассчитывается автоматически на каждой завершённой свече, а для ручного управления предоставлены отдельные методы.
Логика работы
Построение канала
На каждой завершённой свече выбранного CandleType вычисляется индикатор DonchianChannels длиной ChannelLength.
Верхняя и нижняя границы дополнительно расширяются на BufferPoints, умноженные на биржевой шаг цены (PriceStep). Это воспроизводит приём из MQL, где линии сначала ставились на расстоянии 50 пунктов от текущих bid/ask, а затем сдвигались к экстремумам последних баров.
Получившиеся значения доступны через свойства UpperBand и LowerBand, что позволяет отображать их в пользовательских панелях.
Подготовка к пробою
Пока открыта позиция или параметр EnableTrendTrade выключен, автоматическая логика не активна.
Если позиция отсутствует, а цена закрылась внутри канала и держится на расстоянии не менее ActivationPoints × PriceStep от обеих границ, флаг _isArmed становится true. Это полный аналог переменной q из оригинального советника.
Открытие сделок
При активном флаге IsArmed закрытие свечи выше или на уровне UpperBand вызывает рыночную покупку (при условии AllowBuyOpen).
Закрытие ниже или на уровне LowerBand формирует рыночную продажу (если AllowSellOpen разрешён).
После входа стратегия мгновенно сбрасывает состояние подготовки и ждёт, пока цена снова вернётся внутрь канала без открытых позиций.
Управление рисками
Метод StartProtection выставляет защитные ордера. Дистанции вычисляются как TakeProfitPoints × PriceStep и StopLossPoints × PriceStep. При отсутствии шага цены используется значение 0.0001, что соответствует поведению MetaTrader.
Ручной контроль
Объекты BUY_TRIANGLE, SELL_TRIANGLE и CLOSE_ORDER заменены публичными методами TriggerManualBuy(), TriggerManualSell() и ClosePositionManually().
Перед исполнением методы проверяют AllowBuyOpen/AllowSellOpen и состояние подключения через IsFormedAndOnlineAndAllowTrading(), а после исполнения отключают автоматический режим, чтобы ручные позиции не перекрывались мгновенно автоматикой.
Параметры
Параметр
Значение по умолчанию
Описание
CandleType
таймфрейм 30m
Основной поток свечей для расчётов.
ChannelLength
20
Количество баров в канале Дончиана.
BufferPoints
50
Дополнительный запас в пунктах вокруг последней цены закрытия.
ActivationPoints
2
Минимальная дистанция до границ канала перед активацией пробойной логики.
StopLossPoints
100
Размер стоп-лосса в пунктах (переводится в цену через PriceStep).
TakeProfitPoints
100
Размер тейк-профита в пунктах (переводится в цену через PriceStep).
EnableTrendTrade
true
Включает автоматические сделки по пробою. При false доступны только ручные методы.
Volume
1
Объём заявок для автоматических и ручных входов.
Рекомендации по эксплуатации
В MetaTrader пользователь должен был перемещать элементы на графике, чтобы обновить линии. В StockSharp канал пересчитывается после каждой свечи, поэтому ручных действий не требуется.
Свойства UpperBand, LowerBand и IsArmed позволяют строить собственные визуализации состояния стратегии.
Чтобы отключить защитные ордера, установите StopLossPoints или TakeProfitPoints в ноль — это повторяет поведение MQL-версии.
Ручные сделки используют тот же Volume и автоматически получают заданные стопы/профиты.
Для принудительного сброса режима ожидания можно вызвать ClosePositionManually() или дождаться, когда цена вновь окажется внутри канала и выполнит условия активации.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// TrainYourself: Donchian channel breakout strategy.
/// Uses Highest/Lowest as channel, arms after price is inside channel,
/// then trades breakouts.
/// </summary>
public class TrainYourselfStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _channelLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevHighest;
private decimal _prevLowest;
private bool _isArmed;
private decimal _entryPrice;
public TrainYourselfStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_channelLength = Param(nameof(ChannelLength), 20)
.SetDisplay("Channel Length", "Highest/Lowest period.", "Channel");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR for stop distance.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int ChannelLength
{
get => _channelLength.Value;
set => _channelLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHighest = 0;
_prevLowest = 0;
_isArmed = false;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHighest = 0;
_prevLowest = 0;
_isArmed = false;
_entryPrice = 0;
var highest = new Highest { Length = ChannelLength };
var lowest = new Lowest { Length = ChannelLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal highVal, decimal lowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevHighest == 0 || _prevLowest == 0 || atrVal <= 0)
{
_prevHighest = highVal;
_prevLowest = lowVal;
return;
}
var close = candle.ClosePrice;
var upper = _prevHighest;
var lower = _prevLowest;
// Manage position: exit on channel re-entry
if (Position > 0)
{
if (close < (upper + lower) / 2m)
{
SellMarket();
_entryPrice = 0;
_isArmed = false;
}
}
else if (Position < 0)
{
if (close > (upper + lower) / 2m)
{
BuyMarket();
_entryPrice = 0;
_isArmed = false;
}
}
// Arm when price is inside channel
if (Position == 0)
{
if (!_isArmed)
{
var margin = atrVal * 0.2m;
if (close > lower + margin && close < upper - margin)
_isArmed = true;
}
else
{
// Breakout entry
if (close > upper)
{
_entryPrice = close;
BuyMarket();
_isArmed = false;
}
else if (close < lower)
{
_entryPrice = close;
SellMarket();
_isArmed = false;
}
}
}
_prevHighest = highVal;
_prevLowest = lowVal;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class train_yourself_strategy(Strategy):
"""Donchian channel breakout: arm inside channel, trade on breakout, exit at midline."""
def __init__(self):
super(train_yourself_strategy, self).__init__()
self._channel_length = self.Param("ChannelLength", 20).SetDisplay("Channel Length", "Highest/Lowest period", "Channel")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR for stop distance", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(train_yourself_strategy, self).OnReseted()
self._prev_highest = 0
self._prev_lowest = 0
self._is_armed = False
self._entry_price = 0
def OnStarted2(self, time):
super(train_yourself_strategy, self).OnStarted2(time)
self._prev_highest = 0
self._prev_lowest = 0
self._is_armed = False
self._entry_price = 0
highest = Highest()
highest.Length = self._channel_length.Value
lowest = Lowest()
lowest.Length = self._channel_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(highest, lowest, atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle, high_val, low_val, atr_val):
if candle.State != CandleStates.Finished:
return
if self._prev_highest == 0 or self._prev_lowest == 0 or atr_val <= 0:
self._prev_highest = float(high_val)
self._prev_lowest = float(low_val)
return
close = float(candle.ClosePrice)
upper = self._prev_highest
lower = self._prev_lowest
mid = (upper + lower) / 2.0
# Manage position: exit on channel re-entry
if self.Position > 0:
if close < mid:
self.SellMarket()
self._entry_price = 0
self._is_armed = False
elif self.Position < 0:
if close > mid:
self.BuyMarket()
self._entry_price = 0
self._is_armed = False
# Arm when price is inside channel
if self.Position == 0:
if not self._is_armed:
margin = float(atr_val) * 0.2
if close > lower + margin and close < upper - margin:
self._is_armed = True
else:
if close > upper:
self._entry_price = close
self.BuyMarket()
self._is_armed = False
elif close < lower:
self._entry_price = close
self.SellMarket()
self._is_armed = False
self._prev_highest = float(high_val)
self._prev_lowest = float(low_val)
def CreateClone(self):
return train_yourself_strategy()