Стратегия Neural Network Template
Обзор
Стратегия повторяет MQL5-шаблон, в котором значения RSI и MACD передавались в нейронную сеть. В StockSharp отсутствует оригинальный загрузчик нейросети, поэтому вместо «чёрного ящика» используется прозрачная модель оценки, сохраняющая структуру и риск-контроль исходного эксперта. Цель — входить в рынок, когда RSI и MACD совпадают по направлению, а ожидаемое движение достаточно велико.
Индикаторы и данные
- RSI (12 периодов) считается по цене закрытия и соответствует типичной цене из MQL5-версии.
- MACD (12/48/12) используется для оценки гистограммы и силы импульса.
- Таймфрейм настраивается, по умолчанию 5-минутные свечи как в исходном советнике.
Логика торговли
- После закрытия каждой свечи стратегия обновляет скользящее окно значений RSI и гистограммы MACD. Размер окна задаётся параметром
BarsToPattern. - Отклонение RSI от уровня 50 и отклонение гистограммы MACD от её скользящего среднего комбинируются в показатель доверия. Для сжатия диапазона применяется гиперболический тангенс, имитирующий выход нейросети.
- Если абсолютное значение доверия превышает
TradeLevel, а прогнозируемое движение (в пунктах) большеMinTargetPoints, стратегия открывает позицию по рынку в направлении оценки. - Для каждой позиции рассчитывается индивидуальный тейк-профит: прогнозируемое движение умножается на
ProfitMultiply, после чего ограничиваетсяMaxTakeProfitPoints. Стоп-лосс выставляется симметрично в пунктах. - Пока позиция открыта, каждая завершённая свеча проверяет достижение целевых уровней. При касании цены тейк-профита или стоп-лосса позиция закрывается по рынку, а внутреннее состояние сбрасывается.
Параметры
| Параметр | Описание |
|---|---|
BarsToPattern |
Размер скользящего окна для расчётов RSI и MACD. |
TradeLevel |
Минимальная требуемая уверенность (0-1) для открытия сделки. |
ProfitMultiply |
Множитель, применяемый к прогнозу перед ограничением MaxTakeProfitPoints. |
MinTargetPoints |
Минимальный прогноз по пунктам, необходимый для входа. |
MaxTakeProfitPoints |
Максимально допустимый тейк-профит в пунктах. |
StopLossPoints |
Расстояние стоп-лосса от цены входа в пунктах. |
TradeVolume |
Объём каждой рыночной заявки. |
CandleType |
Тип/таймфрейм свечей, которые подписывает стратегия. |
Примечания
- Детерминированная модель уверенности упрощает анализ и сохраняет дух оригинальной нейросетевой идеи.
- Управление тейк-профитом и стоп-лоссом выполняется вручную внутри стратегии, чтобы у каждой сделки оставались собственные целевые уровни, как в версии MQL5.
- Стратегия анализирует сигналы только при отсутствии открытой позиции, полностью копируя однопозиционный режим исходного эксперта.
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>
/// Momentum strategy inspired by a neural-network driven template from MQL5.
/// </summary>
public class NeuralNetworkTemplateStrategy : Strategy
{
private readonly StrategyParam<int> _barsToPattern;
private readonly StrategyParam<int> _maxTakeProfitPoints;
private readonly StrategyParam<int> _minTargetPoints;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<decimal> _profitMultiply;
private readonly StrategyParam<decimal> _tradeLevel;
private readonly StrategyParam<decimal> _volume;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi = null!;
private MovingAverageConvergenceDivergenceSignal _macd = null!;
private readonly Queue<decimal> _rsiHistory = new();
private readonly Queue<decimal> _macdHistory = new();
private decimal _rsiSum;
private decimal _macdSum;
private decimal? _targetPrice;
private decimal? _stopPrice;
private int _positionDirection;
/// <summary>
/// Number of candles used for pattern recognition.
/// </summary>
public int BarsToPattern
{
get => _barsToPattern.Value;
set => _barsToPattern.Value = value;
}
/// <summary>
/// Upper bound for calculated take-profit in points.
/// </summary>
public int MaxTakeProfitPoints
{
get => _maxTakeProfitPoints.Value;
set => _maxTakeProfitPoints.Value = value;
}
/// <summary>
/// Minimum projected move required to open a trade.
/// </summary>
public int MinTargetPoints
{
get => _minTargetPoints.Value;
set => _minTargetPoints.Value = value;
}
/// <summary>
/// Stop-loss distance in points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Multiplier applied to the projected move returned by the scoring model.
/// </summary>
public decimal ProfitMultiply
{
get => _profitMultiply.Value;
set => _profitMultiply.Value = value;
}
/// <summary>
/// Required confidence level before opening a new position.
/// </summary>
public decimal TradeLevel
{
get => _tradeLevel.Value;
set => _tradeLevel.Value = value;
}
/// <summary>
/// Trading volume for every market order.
/// </summary>
public decimal TradeVolume
{
get => _volume.Value;
set => _volume.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="NeuralNetworkTemplateStrategy"/>.
/// </summary>
public NeuralNetworkTemplateStrategy()
{
_barsToPattern = Param(nameof(BarsToPattern), 3)
.SetGreaterThanZero()
.SetDisplay("Bars", "Candles analysed", "Model")
;
_maxTakeProfitPoints = Param(nameof(MaxTakeProfitPoints), 500)
.SetGreaterThanZero()
.SetDisplay("Max TP", "Maximum take-profit in points", "Risk");
_minTargetPoints = Param(nameof(MinTargetPoints), 1)
.SetGreaterThanZero()
.SetDisplay("Min Target", "Minimum projected move in points", "Model");
_stopLossPoints = Param(nameof(StopLossPoints), 300)
.SetGreaterThanZero()
.SetDisplay("Stop-Loss", "Stop-loss distance in points", "Risk");
_profitMultiply = Param(nameof(ProfitMultiply), 0.8m)
.SetNotNegative()
.SetDisplay("Profit Mult", "Take-profit multiplier", "Model");
_tradeLevel = Param(nameof(TradeLevel), 0.1m)
.SetRange(0m, 1m)
.SetDisplay("Trade Level", "Required confidence", "Model");
_volume = Param(nameof(TradeVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("TF", "Working timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null!;
_macd = null!;
_rsiHistory.Clear();
_macdHistory.Clear();
_rsiSum = 0m;
_macdSum = 0m;
_targetPrice = null;
_stopPrice = null;
_positionDirection = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = 12 };
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = 12 },
LongMa = { Length = 48 }
},
SignalMa = { Length = 12 }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_rsi, _macd, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawIndicator(area, _macd);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue rsiValue, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
ManageOpenPosition(candle);
if (!_rsi.IsFormed || !_macd.IsFormed)
return;
var rsiDecimal = rsiValue.ToDecimal();
if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macdComponents)
return;
if (macdComponents.Macd is not decimal macdLine ||
macdComponents.Signal is not decimal signalLine)
return;
UpdateHistory(rsiDecimal, macdLine - signalLine);
if (Position != 0)
return;
EvaluateEntry(candle, rsiDecimal, macdLine, signalLine);
}
private void UpdateHistory(decimal rsiValue, decimal macdHistogram)
{
_rsiHistory.Enqueue(rsiValue);
_rsiSum += rsiValue;
if (_rsiHistory.Count > BarsToPattern)
_rsiSum -= _rsiHistory.Dequeue();
_macdHistory.Enqueue(macdHistogram);
_macdSum += macdHistogram;
if (_macdHistory.Count > BarsToPattern)
_macdSum -= _macdHistory.Dequeue();
}
private void EvaluateEntry(ICandleMessage candle, decimal rsiValue, decimal macdLine, decimal signalLine)
{
if (_rsiHistory.Count < BarsToPattern || _macdHistory.Count < BarsToPattern)
return;
var priceStep = Security?.PriceStep ?? 1m;
if (priceStep <= 0m)
priceStep = 1m;
var normalizedRsi = Clamp((rsiValue - 50m) / 50m, -1m, 1m);
var macdHistogram = macdLine - signalLine;
var macdAverage = _macdHistory.Count == 0 ? 0m : _macdSum / _macdHistory.Count;
var macdDeviation = macdHistogram - macdAverage;
var normalizedMomentum = (decimal)Math.Tanh((double)(macdDeviation * 5m));
var combinedScore = normalizedRsi * 0.6m + normalizedMomentum * 0.4m;
var confidence = Math.Min(1m, Math.Abs(combinedScore));
var projectedMove = macdDeviation * BarsToPattern;
var projectedPoints = projectedMove / priceStep;
if (combinedScore > 0m)
{
if (confidence < TradeLevel)
return;
if (projectedPoints < MinTargetPoints)
return;
var takeProfit = candle.ClosePrice + Math.Min(projectedMove * ProfitMultiply, MaxTakeProfitPoints * priceStep);
var stopLoss = candle.ClosePrice - StopLossPoints * priceStep;
if (takeProfit <= candle.ClosePrice)
return;
BuyMarket(TradeVolume);
_targetPrice = takeProfit;
_stopPrice = stopLoss;
_positionDirection = 1;
}
else if (combinedScore < 0m)
{
if (confidence < TradeLevel)
return;
if (projectedPoints > -MinTargetPoints)
return;
var takeProfit = candle.ClosePrice + Math.Max(projectedMove * ProfitMultiply, -MaxTakeProfitPoints * priceStep);
var stopLoss = candle.ClosePrice + StopLossPoints * priceStep;
if (takeProfit >= candle.ClosePrice)
return;
SellMarket(TradeVolume);
_targetPrice = takeProfit;
_stopPrice = stopLoss;
_positionDirection = -1;
}
}
private void ManageOpenPosition(ICandleMessage candle)
{
if (Position > 0m)
{
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Math.Abs(Position));
ResetTargets();
return;
}
if (_targetPrice.HasValue && candle.HighPrice >= _targetPrice.Value)
{
SellMarket(Math.Abs(Position));
ResetTargets();
}
}
else if (Position < 0m)
{
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetTargets();
return;
}
if (_targetPrice.HasValue && candle.LowPrice <= _targetPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetTargets();
}
}
else if (_positionDirection != 0)
{
ResetTargets();
}
}
private void ResetTargets()
{
_targetPrice = null;
_stopPrice = null;
_positionDirection = 0;
}
private static decimal Clamp(decimal value, decimal min, decimal max)
{
if (value < min)
return min;
if (value > max)
return max;
return value;
}
}
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
from StockSharp.Algo.Indicators import RelativeStrengthIndex, MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
from collections import deque
from datatype_extensions import *
from indicator_extensions import *
import math
class neural_network_template_strategy(Strategy):
def __init__(self):
super(neural_network_template_strategy, self).__init__()
self._bars_to_pattern = self.Param("BarsToPattern", 3).SetGreaterThanZero().SetDisplay("Bars", "Candles analysed", "Model")
self._max_tp_points = self.Param("MaxTakeProfitPoints", 500).SetGreaterThanZero().SetDisplay("Max TP", "Maximum take-profit in points", "Risk")
self._min_target = self.Param("MinTargetPoints", 1).SetGreaterThanZero().SetDisplay("Min Target", "Minimum projected move in points", "Model")
self._sl_points = self.Param("StopLossPoints", 300).SetGreaterThanZero().SetDisplay("Stop-Loss", "Stop-loss distance in points", "Risk")
self._profit_mult = self.Param("ProfitMultiply", 0.8).SetNotNegative().SetDisplay("Profit Mult", "Take-profit multiplier", "Model")
self._trade_level = self.Param("TradeLevel", 0.1).SetDisplay("Trade Level", "Required confidence", "Model")
self._trade_volume = self.Param("TradeVolume", 0.1).SetGreaterThanZero().SetDisplay("Volume", "Order volume", "Trading")
self._candle_type = self.Param("CandleType", tf(5)).SetDisplay("TF", "Working 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(neural_network_template_strategy, self).OnReseted()
self._rsi_history = deque()
self._macd_history = deque()
self._rsi_sum = 0
self._macd_sum = 0
self._target_price = None
self._stop_price = None
self._pos_dir = 0
def OnStarted2(self, time):
super(neural_network_template_strategy, self).OnStarted2(time)
self._rsi_history = deque()
self._macd_history = deque()
self._rsi_sum = 0
self._macd_sum = 0
self._target_price = None
self._stop_price = None
self._pos_dir = 0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = 12
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = 12
self._macd.Macd.LongMa.Length = 48
self._macd.SignalMa.Length = 12
sub = self.SubscribeCandles(self.CandleType)
sub.BindEx(self._rsi, self._macd, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, self._rsi)
self.DrawOwnTrades(area)
def OnProcess(self, candle, rsi_value, macd_value):
if candle.State != CandleStates.Finished:
return
self._manage_position(candle)
if not self._rsi.IsFormed or not self._macd.IsFormed:
return
rsi_dec = float(rsi_value)
macd_line = macd_value.Macd
signal_line = macd_value.Signal
if macd_line is None or signal_line is None:
return
macd_line = float(macd_line)
signal_line = float(signal_line)
histogram = macd_line - signal_line
self._update_history(rsi_dec, histogram)
if self.Position != 0:
return
self._evaluate_entry(candle, rsi_dec, macd_line, signal_line)
def _update_history(self, rsi_val, macd_hist):
bars = self._bars_to_pattern.Value
self._rsi_history.append(rsi_val)
self._rsi_sum += rsi_val
if len(self._rsi_history) > bars:
self._rsi_sum -= self._rsi_history.popleft()
self._macd_history.append(macd_hist)
self._macd_sum += macd_hist
if len(self._macd_history) > bars:
self._macd_sum -= self._macd_history.popleft()
def _evaluate_entry(self, candle, rsi_val, macd_line, signal_line):
bars = self._bars_to_pattern.Value
if len(self._rsi_history) < bars or len(self._macd_history) < bars:
return
price_step = 1.0
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
price_step = float(self.Security.PriceStep)
normalized_rsi = max(-1, min(1, (rsi_val - 50) / 50))
macd_hist = macd_line - signal_line
macd_avg = self._macd_sum / len(self._macd_history) if len(self._macd_history) > 0 else 0
macd_dev = macd_hist - macd_avg
normalized_momentum = math.tanh(macd_dev * 5)
combined = normalized_rsi * 0.6 + normalized_momentum * 0.4
confidence = min(1, abs(combined))
projected_move = macd_dev * bars
projected_points = projected_move / price_step
vol = self._trade_volume.Value
if combined > 0:
if confidence < self._trade_level.Value:
return
if projected_points < self._min_target.Value:
return
tp = candle.ClosePrice + min(projected_move * self._profit_mult.Value, self._max_tp_points.Value * price_step)
sl = candle.ClosePrice - self._sl_points.Value * price_step
if tp <= candle.ClosePrice:
return
self.BuyMarket(vol)
self._target_price = tp
self._stop_price = sl
self._pos_dir = 1
elif combined < 0:
if confidence < self._trade_level.Value:
return
if projected_points > -self._min_target.Value:
return
tp = candle.ClosePrice + max(projected_move * self._profit_mult.Value, -self._max_tp_points.Value * price_step)
sl = candle.ClosePrice + self._sl_points.Value * price_step
if tp >= candle.ClosePrice:
return
self.SellMarket(vol)
self._target_price = tp
self._stop_price = sl
self._pos_dir = -1
def _manage_position(self, candle):
if self.Position > 0:
if self._stop_price is not None and candle.LowPrice <= self._stop_price:
self.SellMarket(Math.Abs(self.Position))
self._reset_targets()
return
if self._target_price is not None and candle.HighPrice >= self._target_price:
self.SellMarket(Math.Abs(self.Position))
self._reset_targets()
elif self.Position < 0:
if self._stop_price is not None and candle.HighPrice >= self._stop_price:
self.BuyMarket(Math.Abs(self.Position))
self._reset_targets()
return
if self._target_price is not None and candle.LowPrice <= self._target_price:
self.BuyMarket(Math.Abs(self.Position))
self._reset_targets()
elif self._pos_dir != 0:
self._reset_targets()
def _reset_targets(self):
self._target_price = None
self._stop_price = None
self._pos_dir = 0
def CreateClone(self):
return neural_network_template_strategy()