Layered Risk Protector Strategy — это прямая конверсия советника MetaTrader «RiskManager». Алгоритм непрерывно отслеживает эквити портфеля и управляет рыночной нагрузкой с помощью индикатора Commodity Channel Index (CCI), множителей Average True Range (ATR) и послойной модели наращивания позиций. Когда риск-показатели опускаются ниже заданных порогов, стратегия автоматически переходит в режим хеджирования, фиксирует позиции при достижении целевых уровней прибыли или просадки и при необходимости закрывает их на уровне безубыточности.
Логика торговли
Индикаторные условия – стратегия подписывается на основную серию свечей (таймфрейм задаётся пользователем) и рассчитывает:
CCI с заданным периодом. Для открытия длинной позиции значение CCI должно опуститься ниже отрицательного порога, для короткой — подняться выше положительного.
ATR с фиксированным периодом 14 для вычисления волатильностных расстояний стоп-лосса и тейк-профита каждой открываемой ступени.
Скользящее среднее объёмов за последние 50 завершённых свечей. Торговля разрешается только когда среднее превышает объём предыдущей свечи — это воспроизводит фильтр «Active» из оригинала.
Послойные входы – максимальная нагрузка распределяется по настраиваемому количеству слоёв. Каждый новый ордер использует объём MaxVolume / Layers. Дополнительные входы блокируются, если относительное использование слоёв (Orders / Layers * 100) превышает текущий уровень «здоровья» системы.
Управление ордерами – каждая ступень хранит цену входа и уровни стоп-лосса/тейк-профита, рассчитанные по ATR. На каждой завершённой свече проверяются максимум и минимум для определения срабатывания защитных уровней и закрытия ордеров.
Режим хеджирования – если MultiPairTrading = false и уровень здоровья падает ниже HedgeLevel, стратегия запоминает текущее количество ордеров и открывает встречные позиции до выполнения требования по HedgeRatio. Когда здоровье восстанавливается выше порога, хедж автоматически отключается.
Контроль эквити – реализованы следующие защиты, повторяющие поведение советника:
Прибыльная цель как добавка к начальному капиталу.
Скользящий уровень «Close Equity», который после полного закрытия позиций повышается на величину CloseProfitBuffer от текущего баланса.
Опциональное закрытие на уровне безубыточности при достижении сохранённого значения капитала.
Ручной переключатель HardClose, моментально сворачивающий все позиции и приостанавливающий торговлю.
Параметры
AllowLong / AllowShort – разрешение на открытие длинных или коротких позиций.
MaxVolume – суммарный объём позиций, распределяемый между слоями.
Layers – максимальное число одновременно открытых слоёв.
CciLength / CciLevel – период и порог CCI.
StopLossMultiple / TakeProfitMultiple – множители ATR для расчёта стоп-лосса и тейк-профита каждого слоя.
CloseProfitBuffer – прибыль, добавляемая к балансу при обновлении уровня Close Equity; также используется при расчёте безубыточного капитала.
ManualCapital – ручное задание стартового капитала для риск-менеджмента (0 — использовать текущий баланс портфеля при запуске).
RiskLimit – максимально допустимая просадка от стартового капитала.
ProfitTarget – добавочная прибыльная цель, при достижении которой торговля приостанавливается.
MultiPairTrading – если true, внутреннее хеджирование не запускается даже при снижении здоровья ниже порога.
HedgeLevel / HedgeRatio – уровень здоровья для старта хеджирования и требуемое соотношение дополнительных ордеров в режиме хеджа.
CloseAtBreakEven – включает логику выхода по безубытку.
HardClose – принудительно закрывает позиции и блокирует торговлю, пока параметр активен.
CandleType – тип свечей, используемых для расчётов и принятия решений.
Примечания
Предполагается мгновенное исполнение рыночных ордеров; в режиме тестирования фактическая модель исполнения определяется настройками StockSharp.
Данные об эквити и балансе берутся из подключённого портфеля (Portfolio.CurrentValue, Portfolio.CurrentBalance). Убедитесь, что портфель синхронизирован с торгуемым инструментом.
Режим хеджирования открывает дополнительные сделки по тому же инструменту. Проверьте, допускает ли брокер или тестовый стенд противоположные позиции.
Логика безубыточности использует значение CloseProfitBuffer, повторяя оригинальный параметр MetaTrader ClosePL.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Risk Manager Layered" MetaTrader expert.
/// Uses CCI crossover for entry with position management. CCI above level -> sell,
/// CCI below negative level -> buy, with volume-based confirmation.
/// </summary>
public class LayeredRiskProtectorStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cciLength;
private readonly StrategyParam<decimal> _cciLevel;
private CommodityChannelIndex _cci;
private decimal? _prevCci;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int CciLength
{
get => _cciLength.Value;
set => _cciLength.Value = value;
}
public decimal CciLevel
{
get => _cciLevel.Value;
set => _cciLevel.Value = value;
}
public LayeredRiskProtectorStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series", "General");
_cciLength = Param(nameof(CciLength), 100)
.SetGreaterThanZero()
.SetDisplay("CCI Length", "CCI indicator period", "Indicators");
_cciLevel = Param(nameof(CciLevel), 150m)
.SetGreaterThanZero()
.SetDisplay("CCI Level", "CCI threshold for entries", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_cci = new CommodityChannelIndex { Length = CciLength };
_prevCci = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_cci, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _cci);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_cci.IsFormed)
{
_prevCci = cciValue;
return;
}
if (_prevCci is null)
{
_prevCci = cciValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// CCI crosses below -level -> buy
var buyCross = _prevCci.Value >= -CciLevel && cciValue < -CciLevel;
// CCI crosses above +level -> sell
var sellCross = _prevCci.Value <= CciLevel && cciValue > CciLevel;
if (buyCross)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (sellCross)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevCci = cciValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_cci = null;
_prevCci = null;
base.OnReseted();
}
}
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 CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class layered_risk_protector_strategy(Strategy):
def __init__(self):
super(layered_risk_protector_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._cci_length = self.Param("CciLength", 100)
self._cci_level = self.Param("CciLevel", 150.0)
self._prev_cci = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CciLength(self):
return self._cci_length.Value
@CciLength.setter
def CciLength(self, value):
self._cci_length.Value = value
@property
def CciLevel(self):
return self._cci_level.Value
@CciLevel.setter
def CciLevel(self, value):
self._cci_level.Value = value
def OnReseted(self):
super(layered_risk_protector_strategy, self).OnReseted()
self._prev_cci = None
def OnStarted2(self, time):
super(layered_risk_protector_strategy, self).OnStarted2(time)
self._prev_cci = None
cci = CommodityChannelIndex()
cci.Length = self.CciLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._process_candle).Start()
def _process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
cci_val = float(cci_value)
if self._prev_cci is None:
self._prev_cci = cci_val
return
cci_level = float(self.CciLevel)
# CCI crosses below -level -> buy
buy_cross = self._prev_cci >= -cci_level and cci_val < -cci_level
# CCI crosses above +level -> sell
sell_cross = self._prev_cci <= cci_level and cci_val > cci_level
if buy_cross:
if self.Position <= 0:
self.BuyMarket()
elif sell_cross:
if self.Position >= 0:
self.SellMarket()
self._prev_cci = cci_val
def CreateClone(self):
return layered_risk_protector_strategy()