Стратегия Risk Monitor
Обзор
Risk Monitor — порт MetaTrader 4 советника risk.mq4. Оригинальный скрипт не открывал сделок, а рассчитывал доступный объём
в зависимости от баланса счёта и заданного процента риска. Версия для StockSharp сохраняет тот же подход: постоянно диагностирует
состояние счёта, вычисляет рекомендуемые размеры позиций, отслеживает плавающую и реализованную прибыль и публикует результаты
непосредственно в поле комментария стратегии.
В отличие от типичных торговых систем Risk Monitor не выставляет заявки самостоятельно. Он служит монитором: показывает текущую
нагрузку по позиции, оставшуюся мощность в рамках выбранного риска и результат закрытых сделок. При любом изменении позиции, PnL
или собственных сделок комментарий обновляется, поэтому данные всегда актуальны.
Логика расчётов
В комментарии выводятся три группы показателей:
- Базовый объём —
баланс / 1000, приведённый к минимальному шагу объёма инструмента. Это повторяет логику MT4, где на каждые
1000 денежных единиц приходится один лот.
- Рисковый объём — базовый объём, умноженный на
Risk % / 100 и также округлённый по шагу объёма. Значение показывает, сколько
лотов можно открыть, не превышая допустимую долю риска.
- Открытый объём и разница — сравнение абсолютной нетто-позиции с рисковым объёмом. Если фактическая позиция ниже лимита,
разница показывает, сколько лотов ещё доступно. Незначительные отрицательные остатки (меньше шага объёма) обнуляются, чтобы не
создавать визуальный шум.
Отдельно ведётся учёт прибыли:
- Плавающий PnL — берётся из свойства
PnL стратегии, дополнительно переводится в проценты от текущей стоимости портфеля.
- Реализованная прибыль — накапливается по собственным сделкам. Для каждой закрывающей сделки вычисляется результат, учитывается
комиссия, прибыль и убыток суммируются раздельно, затем складываются и конвертируются в долю от капитала.
Параметры
- Risk % — доля баланса, которую разрешено задействовать в новых позициях. Значение по умолчанию —
10. Параметр доступен для
оптимизации, что позволяет быстро протестировать разные уровни риска.
Формат комментария
Стратегия формирует три строки:
Base lots, Risk lots, Open lots, Lots to adjust — показатели позиционирования.
Risk, Floating PnL — установленный риск и текущая плавающая прибыль (в валюте и процентах).
Realized profit — совокупный результат закрытых сделок и его доля от капитала.
Все значения округляются аналогично оригинальному скрипту: учитывается шаг объёма инструмента, денежные величины отображаются с двумя
знаками после запятой. Поскольку информация находится в комментарии, её видно прямо на графике или в таблице стратегий без открытия
дополнительных окон.
Рекомендации по использованию
- Назначайте стратегию на тот инструмент, по которому нужно контролировать нагрузку. StockSharp работает в режиме неттинга, поэтому
хеджевые позиции MT4 не поддерживаются.
- Стратегия корректно реагирует на ручные операции — как только приходит подтверждение собственной сделки, статистика обновляется.
- При остановке или сбросе стратегия автоматически очищает комментарий, чтобы не оставлять устаревшие данные между сессиями.
- В пакете API доступна только реализация на C#; Python-версии нет.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Risk-aware EMA crossover strategy inspired by the original MT4 Risk Monitor script.
/// Uses a risk percentage of account balance to size positions, combined with EMA crossover signals.
/// Tracks realized gains/losses and adjusts lot sizing accordingly.
/// </summary>
public class RiskMonitorStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private decimal? _prevFast;
private decimal? _prevSlow;
/// <summary>
/// Candle type used for signal detection.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Percentage of the account balance allocated to new positions.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Take profit distance in absolute points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop loss distance in absolute points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="RiskMonitorStrategy"/>.
/// </summary>
public RiskMonitorStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle series used for calculations", "General");
_riskPercent = Param(nameof(RiskPercent), 10m)
.SetGreaterThanZero()
.SetDisplay("Risk %", "Portion of balance used to size positions", "Risk Management")
.SetOptimize(5m, 30m, 5m);
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
.SetOptimize(5, 30, 5);
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
.SetOptimize(20, 80, 10);
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
.SetNotNegative()
.SetDisplay("Take Profit", "Absolute take profit distance", "Risk");
_stopLossPoints = Param(nameof(StopLossPoints), 500m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Absolute stop loss distance", "Risk");
Volume = 1;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = null;
_slowEma = null;
_prevFast = null;
_prevSlow = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
_fastEma = new ExponentialMovingAverage { Length = FastPeriod };
_slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastEma, _slowEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastEma);
DrawIndicator(area, _slowEma);
DrawOwnTrades(area);
}
if (TakeProfitPoints > 0 || StopLossPoints > 0)
{
var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
StartProtection(tp, sl);
}
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
var pf = _prevFast;
var ps = _prevSlow;
_prevFast = fastValue;
_prevSlow = slowValue;
if (pf == null || ps == null)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Detect crossover
var prevDiff = pf.Value - ps.Value;
var currDiff = fastValue - slowValue;
if (prevDiff <= 0 && currDiff > 0)
{
// Bullish crossover
if (Position < 0)
BuyMarket(Math.Abs(Position));
if (Position == 0)
BuyMarket(Volume);
}
else if (prevDiff >= 0 && currDiff < 0)
{
// Bearish crossover
if (Position > 0)
SellMarket(Position);
if (Position == 0)
SellMarket(Volume);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class risk_monitor_strategy(Strategy):
def __init__(self):
super(risk_monitor_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 10).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._tp_points = self.Param("TakeProfitPoints", 500.0).SetNotNegative().SetDisplay("Take Profit", "TP distance", "Risk")
self._sl_points = self.Param("StopLossPoints", 500.0).SetNotNegative().SetDisplay("Stop Loss", "SL distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle 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(risk_monitor_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
self._entry_price = 0
def OnStarted2(self, time):
super(risk_monitor_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
self._entry_price = 0
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_period.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._slow_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast_ema, slow_ema, self.OnProcess).Start()
tp_val = float(self._tp_points.Value)
sl_val = float(self._sl_points.Value)
tp = Unit(tp_val, UnitTypes.Absolute) if tp_val > 0 else None
sl = Unit(sl_val, UnitTypes.Absolute) if sl_val > 0 else None
self.StartProtection(tp, sl)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
pf = self._prev_fast
ps = self._prev_slow
self._prev_fast = fast_val
self._prev_slow = slow_val
if pf is None or ps is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
prev_diff = pf - ps
curr_diff = fast_val - slow_val
if prev_diff <= 0 and curr_diff > 0:
if self.Position < 0:
self.BuyMarket(abs(self.Position))
if self.Position == 0:
self.BuyMarket(self.Volume)
elif prev_diff >= 0 and curr_diff < 0:
if self.Position > 0:
self.SellMarket(self.Position)
if self.Position == 0:
self.SellMarket(self.Volume)
def CreateClone(self):
return risk_monitor_strategy()