Ver en GitHub

Estrategia Adaptativa Perceptron

Descripción General

Esta estrategia es un port a StockSharp del asesor experto de MetaTrader 5 Perceptron.mq5.
Cinco señales discretas de indicadores se combinan a través de un perceptrón de dos capas. Cada operación registra el estado del indicador y, una vez cerrada la posición, los pesos sinápticos se refuerzan o penalizan dependiendo del beneficio obtenido. El comportamiento imita el bucle de autoaprendizaje del EA original aprovechando la API de velas de alto nivel de StockSharp.

Capa de indicadores

Código Descripción Lógica de señal
IND1 Cruce de medias móviles simples rápida/lenta +1 cuando la MA rápida cruza por encima de la MA lenta en la barra anterior, −1 en un cruce descendente, de lo contrario 0.
IND2 Índice de Fuerza Relativa (RSI) +1 cuando el RSI sale de la zona de sobrevendida (cruza por encima de 30), −1 cuando el RSI sale de la zona de sobrecomprada (cruza por debajo de 70).
IND3 Índice del Canal de Materias Primas (CCI) +1 en un cruce por encima de −100, −1 en un cruce por debajo de +100.
IND4 Pendiente de la media móvil simple corta +1 si la MA corta aumentó entre las dos barras anteriores, −1 si disminuyó.
IND5 Color del momentum del Awesome Oscillator +1 cuando el histograma aumenta en comparación con el valor anterior (color alcista), −1 cuando disminuye.

Todos los indicadores se evalúan en velas cerradas. Se mantienen búferes históricos internamente para replicar el windowing CopyBuffer utilizado en el script MQL5.

Arquitectura del perceptrón

  • Cinco neuronas ocultas (NN1NN5) combinan cuatro indicadores cada una, imitando el cableado en el EA.
  • Cada neurona tiene su propio diccionario de pesos sinápticos más un peso de sesgo (NNS1NNS5).
  • La activación final brainReturn es la suma ponderada de las salidas de las neuronas.
    • brainReturn > 0 → solicitar una entrada larga (si la operación anterior tampoco fue larga).
    • brainReturn < 0 → solicitar una entrada corta (si la operación anterior tampoco fue corta).
  • Las posiciones se abren solo con órdenes de mercado cuando no hay posición activa.

Gestión de posición

  • El precio de entrada, dirección y estados de indicador/neurona se capturan en cada ejecución.
  • Los desplazamientos de take-profit y stop-loss se aplican en unidades de precio absoluto (p. ej. 0.0004 para 4 puntos en un par Forex con 5 decimales).
    Cuando se abre una nueva vela tras la entrada:
    • Para largos, primero se compara el máximo con el precio de take-profit, luego el mínimo con el stop-loss.
    • Para cortos, primero se compara el mínimo con el precio de take-profit, luego el máximo con el stop-loss.
    • Si ambos niveles se superan dentro de la misma vela, el take-profit tiene prioridad, coincidiendo con el comportamiento optimista del EA original.
  • Una vez detectada una salida, la estrategia cierra la posición con una orden de mercado y calcula el beneficio realizado usando el nivel TP/SL correspondiente.

Actualización adaptativa de pesos

Cuando se cierra una operación, los estados de indicador y neurona capturados se replayan:

  1. Se determina directionSign (−1 para largos, +1 para cortos) y outcomeSign (signo del PnL realizado).
  2. Los pesos de sesgo se ajustan dentro de [SinMin, SinMax]:
    • Si sign(neuronOutput) * directionSign es positivo, el sesgo sigue el resultado de la operación (aumenta en ganancias, disminuye en pérdidas).
    • De lo contrario, el sesgo se mueve en sentido opuesto al resultado.
  3. Los pesos sinápticos se comportan de manera similar pero permanecen sin límites: las señales alineadas con la dirección de la posición reciben refuerzo en ganancias y penalizaciones en pérdidas, mientras que las señales opuestas hacen lo inverso.
  4. Las señales almacenadas se borran para evitar el uso accidental.

Esto generaliza las más de 1.500 líneas de gestión condicional de sinapsis del EA en una rutina de refuerzo compacta.

Parámetros

Parámetro Predeterminado Descripción
CandleType Marco temporal de 1 minuto Suscripción de velas utilizada por la estrategia.
FastMaLength 5 Período de la SMA rápida en la señal de cruce.
SlowMaLength 9 Período de la SMA lenta.
RsiLength 14 Período de cálculo del RSI.
CciLength 14 Período de cálculo del CCI.
SlopeMaLength 5 Período de la MA utilizada para la detección de pendiente.
AoShortLength 5 Período corto del Awesome Oscillator.
AoLongLength 34 Período largo del Awesome Oscillator.
StopLossOffset 0.001 Distancia de stop-loss en unidades de precio absoluto (0 deshabilita el stop).
TakeProfitOffset 0.0004 Distancia de take-profit en unidades de precio absoluto (0 deshabilita el objetivo).
SinMax 5 Límite superior para los pesos de sesgo neuronal.
SinMin 0 Límite inferior para los pesos de sesgo neuronal.
SinPlusStep 0.03 Incremento de refuerzo positivo.
SinMinusStep 0.03 Decremento de refuerzo negativo.

Todos los parámetros numéricos están expuestos como StrategyParam<T> y pueden optimizarse en StockSharp Designer.

Notas de implementación

  • Usa la API de suscripción de velas de alto nivel con vinculación multi-indicador.
  • Se emplea gestión manual de operaciones para que los precios realizados sean conocidos al actualizar las sinapsis.
  • Los historiales de indicadores se almacenan con campos anulables para asegurar que las señales solo se activen después de la formación completa.
  • El búfer de color del Awesome Oscillator en el EA se aproxima comparando los valores actuales y anteriores del histograma.
  • La salida de gráfico dibuja la serie de velas más las medias móviles rápida y lenta. Los marcadores de operaciones muestran el comportamiento adaptativo en tiempo real.

Limitaciones y supuestos

  • Los stops y objetivos se evalúan una vez por vela completada; el orden intrabar de los eventos es desconocido, por lo que se da prioridad al objetivo de ganancia cuando se alcanzan ambos umbrales.
  • Los pesos de indicadores no están acotados como en el EA original y pueden crecer considerablemente durante ciclos de refuerzo prolongados.
  • El LastTradeType del EA original nunca se reiniciaba; en este port se borra después de cada salida para que las operaciones consecutivas en la misma dirección sigan siendo posibles.
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>
/// Adaptive multi-layer perceptron strategy converted from the MetaTrader 5 "Perceptron" expert advisor.
/// Combines five discrete indicator signals and tunes their synaptic weights after every completed trade.
/// </summary>
public class PerceptronAdaptiveStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossOffset;
	private readonly StrategyParam<decimal> _takeProfitOffset;
	private readonly StrategyParam<int> _sinMax;
	private readonly StrategyParam<int> _sinMin;
	private readonly StrategyParam<decimal> _sinPlus;
	private readonly StrategyParam<decimal> _sinMinus;
	private readonly StrategyParam<int> _fastMaLength;
	private readonly StrategyParam<int> _slowMaLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _cciLength;
	private readonly StrategyParam<int> _slopeMaLength;
	private readonly StrategyParam<int> _aoShortLength;
	private readonly StrategyParam<int> _aoLongLength;
	private readonly StrategyParam<DataType> _candleType;

	private decimal[] _baseWeights = new decimal[5];
	// Use a flat 5x6 array instead of Dictionary[] to avoid clone validation issues.
	// Index: [neuronIndex, indicatorIndex] where indicatorIndex is 1-5 (index 0 unused).
	private decimal[,] _indicatorWeights = new decimal[5, 6];

	private static readonly int[][] _neuronIndicators =
	{
		new[] { 2, 3, 4, 5 },
		new[] { 1, 3, 4, 5 },
		new[] { 1, 2, 4, 5 },
		new[] { 1, 2, 3, 5 },
		new[] { 1, 2, 3, 4 },
	};

	private int[] _lastIndicatorSignals = new int[5];
	private decimal[] _lastNeuronOutputs = new decimal[5];

	private SimpleMovingAverage _fastMa = null!;
	private SimpleMovingAverage _slowMa = null!;
	private RelativeStrengthIndex _rsi = null!;
	private CommodityChannelIndex _cci = null!;
	private SimpleMovingAverage _slopeMa = null!;
	private AwesomeOscillator _ao = null!;

	private decimal? _prevFastMa;
	private decimal? _prevPrevFastMa;
	private decimal? _prevSlowMa;
	private decimal? _prevRsi;
	private decimal? _prevPrevRsi;
	private decimal? _prevCci;
	private decimal? _prevPrevCci;
	private decimal? _prevSlopeMa;
	private decimal? _prevPrevSlopeMa;
	private decimal? _prevAo;

	private bool _hasLastSignals;
	private int _lastTradeDirection;
	private decimal _entryPrice;
	private decimal _stopLossPrice;
	private decimal _takeProfitPrice;
	private bool _isLongPosition;
	private DateTimeOffset? _entryCandleTime;

	/// <summary>
	/// Stop-loss distance in absolute price units.
	/// </summary>
	public decimal StopLossOffset
	{
		get => _stopLossOffset.Value;
		set => _stopLossOffset.Value = value;
	}

	/// <summary>
	/// Take-profit distance in absolute price units.
	/// </summary>
	public decimal TakeProfitOffset
	{
		get => _takeProfitOffset.Value;
		set => _takeProfitOffset.Value = value;
	}

	/// <summary>
	/// Upper boundary for neuron bias weights.
	/// </summary>
	public int SinMax
	{
		get => _sinMax.Value;
		set => _sinMax.Value = value;
	}

	/// <summary>
	/// Lower boundary for neuron bias weights.
	/// </summary>
	public int SinMin
	{
		get => _sinMin.Value;
		set => _sinMin.Value = value;
	}

	/// <summary>
	/// Increment applied when reinforcing synaptic weights.
	/// </summary>
	public decimal SinPlusStep
	{
		get => _sinPlus.Value;
		set => _sinPlus.Value = value;
	}

	/// <summary>
	/// Decrement applied when penalizing synaptic weights.
	/// </summary>
	public decimal SinMinusStep
	{
		get => _sinMinus.Value;
		set => _sinMinus.Value = value;
	}

	/// <summary>
	/// Length of the fast moving average used in the crossover signal.
	/// </summary>
	public int FastMaLength
	{
		get => _fastMaLength.Value;
		set => _fastMaLength.Value = value;
	}

	/// <summary>
	/// Length of the slow moving average used in the crossover signal.
	/// </summary>
	public int SlowMaLength
	{
		get => _slowMaLength.Value;
		set => _slowMaLength.Value = value;
	}

	/// <summary>
	/// RSI lookback length.
	/// </summary>
	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	/// <summary>
	/// CCI lookback length.
	/// </summary>
	public int CciLength
	{
		get => _cciLength.Value;
		set => _cciLength.Value = value;
	}

	/// <summary>
	/// Length of the smoothing average used for the trend slope signal.
	/// </summary>
	public int SlopeMaLength
	{
		get => _slopeMaLength.Value;
		set => _slopeMaLength.Value = value;
	}

	/// <summary>
	/// Short period of the Awesome Oscillator.
	/// </summary>
	public int AoShortLength
	{
		get => _aoShortLength.Value;
		set => _aoShortLength.Value = value;
	}

	/// <summary>
	/// Long period of the Awesome Oscillator.
	/// </summary>
	public int AoLongLength
	{
		get => _aoLongLength.Value;
		set => _aoLongLength.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="PerceptronAdaptiveStrategy"/>.
	/// </summary>
	public PerceptronAdaptiveStrategy()
	{
		_stopLossOffset = Param(nameof(StopLossOffset), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss Offset", "Stop-loss distance in absolute price units", "Risk Management")
			
			.SetOptimize(0.0005m, 0.005m, 0.0005m);

		_takeProfitOffset = Param(nameof(TakeProfitOffset), 300m)
			.SetNotNegative()
			.SetDisplay("Take Profit Offset", "Take-profit distance in absolute price units", "Risk Management")
			
			.SetOptimize(0.0004m, 0.006m, 0.0004m);

		_sinMax = Param(nameof(SinMax), 5)
			.SetDisplay("Synapse Upper Bound", "Maximum value for neuron bias weights", "Neural Network")
			
			.SetOptimize(3, 10, 1);

		_sinMin = Param(nameof(SinMin), 0)
			.SetDisplay("Synapse Lower Bound", "Minimum value for neuron bias weights", "Neural Network")
			
			.SetOptimize(-5, 0, 1);

		_sinPlus = Param(nameof(SinPlusStep), 0.03m)
			.SetGreaterThanZero()
			.SetDisplay("Positive Adjustment", "Increment applied when trade result is favorable", "Neural Network")
			
			.SetOptimize(0.01m, 0.1m, 0.01m);

		_sinMinus = Param(nameof(SinMinusStep), 0.03m)
			.SetGreaterThanZero()
			.SetDisplay("Negative Adjustment", "Decrement applied when trade result is unfavorable", "Neural Network")
			
			.SetOptimize(0.01m, 0.1m, 0.01m);

		_fastMaLength = Param(nameof(FastMaLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA Length", "Fast simple moving average length", "Indicators")
			
			.SetOptimize(3, 20, 1);

		_slowMaLength = Param(nameof(SlowMaLength), 9)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Length", "Slow simple moving average length", "Indicators")
			
			.SetOptimize(5, 40, 1);

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Length", "Relative Strength Index period", "Indicators")
			
			.SetOptimize(7, 30, 1);

		_cciLength = Param(nameof(CciLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("CCI Length", "Commodity Channel Index period", "Indicators")
			
			.SetOptimize(7, 40, 1);

		_slopeMaLength = Param(nameof(SlopeMaLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Slope MA Length", "Simple moving average used for slope detection", "Indicators")
			
			.SetOptimize(3, 20, 1);

		_aoShortLength = Param(nameof(AoShortLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("AO Short Length", "Short period for the Awesome Oscillator", "Indicators")
			
			.SetOptimize(3, 10, 1);

		_aoLongLength = Param(nameof(AoLongLength), 34)
			.SetGreaterThanZero()
			.SetDisplay("AO Long Length", "Long period for the Awesome Oscillator", "Indicators")
			
			.SetOptimize(20, 60, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for calculations", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		ResetState();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		ResetState();

		_fastMa = new SMA { Length = FastMaLength };
		_slowMa = new SMA { Length = SlowMaLength };
		_rsi = new RelativeStrengthIndex { Length = RsiLength };
		_cci = new CommodityChannelIndex { Length = CciLength };
		_slopeMa = new SMA { Length = SlopeMaLength };
		_ao = new AwesomeOscillator
		{
			ShortMa = { Length = AoShortLength },
			LongMa = { Length = AoLongLength },
		};

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(_fastMa, _slowMa, _rsi, _cci, _slopeMa, _ao, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastMa);
			DrawIndicator(area, _slowMa);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastMaValue, decimal slowMaValue, decimal rsiValue, decimal cciValue, decimal slopeMaValue, decimal aoValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var maSignal = UpdateMaSignal(fastMaValue, slowMaValue);
		var rsiSignal = UpdateRsiSignal(rsiValue);
		var cciSignal = UpdateCciSignal(cciValue);
		var slopeSignal = UpdateSlopeSignal(slopeMaValue);
		var aoSignal = UpdateAoSignal(aoValue);

		HandlePositionManagement(candle);

		if (!_fastMa.IsFormed || !_slowMa.IsFormed || !_rsi.IsFormed || !_cci.IsFormed)
			return;

		if (Position != 0)
			return;

		var indicatorSignals = new[] { maSignal, rsiSignal, cciSignal, slopeSignal, aoSignal };
		var neuronOutputs = CalculateNeuronOutputs(indicatorSignals);
		var brainReturn = CalculateBrainReturn(neuronOutputs);

		if (brainReturn > 0m && _lastTradeDirection != 2)
		{
			OpenPosition(true, candle.ClosePrice, candle.OpenTime, indicatorSignals, neuronOutputs);
		}
		else if (brainReturn < 0m && _lastTradeDirection != 1)
		{
			OpenPosition(false, candle.ClosePrice, candle.OpenTime, indicatorSignals, neuronOutputs);
		}
	}

	private void OpenPosition(bool isLong, decimal entryPrice, DateTimeOffset candleOpenTime, IReadOnlyList<int> indicatorSignals, IReadOnlyList<decimal> neuronOutputs)
	{
		var volume = Volume;

		if (isLong)
		{
			BuyMarket();
			_lastTradeDirection = 2;
		}
		else
		{
			SellMarket();
			_lastTradeDirection = 1;
		}

		_entryPrice = entryPrice;
		_isLongPosition = isLong;
		_entryCandleTime = candleOpenTime;

		var stopOffset = StopLossOffset;
		var takeOffset = TakeProfitOffset;

		_stopLossPrice = 0m;
		_takeProfitPrice = 0m;

		if (stopOffset > 0m)
		{
			_stopLossPrice = isLong ? entryPrice - stopOffset : entryPrice + stopOffset;
		}

		if (takeOffset > 0m)
		{
			_takeProfitPrice = isLong ? entryPrice + takeOffset : entryPrice - takeOffset;
		}

		_hasLastSignals = true;

		for (var i = 0; i < indicatorSignals.Count; ++i)
			_lastIndicatorSignals[i] = indicatorSignals[i];

		for (var i = 0; i < neuronOutputs.Count; ++i)
			_lastNeuronOutputs[i] = neuronOutputs[i];
	}

	private void HandlePositionManagement(ICandleMessage candle)
	{
		if (Position == 0 || _entryCandleTime is null)
			return;

		if (candle.OpenTime <= _entryCandleTime.Value)
			return;

		var hasExit = false;
		decimal exitPrice = 0m;

		if (_isLongPosition)
		{
			if (_takeProfitPrice > 0m && candle.HighPrice >= _takeProfitPrice)
			{
				exitPrice = _takeProfitPrice;
				hasExit = true;
			}
			else if (_stopLossPrice > 0m && candle.LowPrice <= _stopLossPrice)
			{
				exitPrice = _stopLossPrice;
				hasExit = true;
			}
		}
		else
		{
			if (_takeProfitPrice > 0m && candle.LowPrice <= _takeProfitPrice)
			{
				exitPrice = _takeProfitPrice;
				hasExit = true;
			}
			else if (_stopLossPrice > 0m && candle.HighPrice >= _stopLossPrice)
			{
				exitPrice = _stopLossPrice;
				hasExit = true;
			}
		}

		if (!hasExit)
			return;

		if (Position > 0)
			SellMarket();
		else if (Position < 0)
			BuyMarket();

		var profit = _isLongPosition ? exitPrice - _entryPrice : _entryPrice - exitPrice;

		if (_hasLastSignals)
		{
			AdjustWeights(_isLongPosition, profit);
		}

		ResetAfterExit();
	}

	private void AdjustWeights(bool wasLongTrade, decimal profit)
	{
		var outcomeSign = Math.Sign(profit);
		if (outcomeSign == 0)
			return;

		var directionSign = wasLongTrade ? -1 : 1;
		var sinPlus = SinPlusStep;
		var sinMinus = SinMinusStep;
		var sinMax = (decimal)SinMax;
		var sinMin = (decimal)SinMin;

		for (var neuronIndex = 0; neuronIndex < _baseWeights.Length; neuronIndex++)
		{
			var lastOutput = _lastNeuronOutputs[neuronIndex];
			var neuronSign = Math.Sign(lastOutput);

			if (neuronSign != 0)
			{
				var product = neuronSign * directionSign;

				if (product > 0)
				{
					if (outcomeSign > 0)
					{
						_baseWeights[neuronIndex] = Math.Min(_baseWeights[neuronIndex] + sinPlus, sinMax);
					}
					else
					{
						_baseWeights[neuronIndex] = Math.Max(_baseWeights[neuronIndex] - sinMinus, sinMin);
					}
				}
				else if (product < 0)
				{
					if (outcomeSign > 0)
					{
						_baseWeights[neuronIndex] = Math.Max(_baseWeights[neuronIndex] - sinMinus, sinMin);
					}
					else
					{
						_baseWeights[neuronIndex] = Math.Min(_baseWeights[neuronIndex] + sinPlus, sinMax);
					}
				}
			}

			foreach (var indicatorIndex in _neuronIndicators[neuronIndex])
			{
				var indicatorSignal = _lastIndicatorSignals[indicatorIndex - 1];
				if (indicatorSignal == 0)
					continue;

				var product = indicatorSignal * directionSign;

				if (product > 0)
				{
					_indicatorWeights[neuronIndex, indicatorIndex] += outcomeSign > 0 ? sinPlus : -sinMinus;
				}
				else if (product < 0)
				{
					_indicatorWeights[neuronIndex, indicatorIndex] += outcomeSign > 0 ? -sinMinus : sinPlus;
				}
			}
		}
	}

	private decimal[] CalculateNeuronOutputs(IReadOnlyList<int> indicatorSignals)
	{
		var outputs = new decimal[_baseWeights.Length];

		for (var neuronIndex = 0; neuronIndex < outputs.Length; neuronIndex++)
		{
			var sum = 0m;

			foreach (var indicatorIndex in _neuronIndicators[neuronIndex])
			{
				var signal = indicatorSignals[indicatorIndex - 1];
				if (signal == 0)
					continue;

				var weight = _indicatorWeights[neuronIndex, indicatorIndex];
				sum += weight * signal;
			}

			outputs[neuronIndex] = sum;
		}

		return outputs;
	}

	private decimal CalculateBrainReturn(IReadOnlyList<decimal> neuronOutputs)
	{
		var total = 0m;
		for (var i = 0; i < neuronOutputs.Count; ++i)
			total += neuronOutputs[i] * _baseWeights[i];
		return total;
	}

	private int UpdateMaSignal(decimal fastMaValue, decimal slowMaValue)
	{
		if (!_fastMa.IsFormed || !_slowMa.IsFormed)
		{
			_prevPrevFastMa = _prevFastMa;
			_prevFastMa = fastMaValue;
			_prevSlowMa = slowMaValue;
			return 0;
		}

		if (_prevFastMa is null || _prevPrevFastMa is null || _prevSlowMa is null)
		{
			_prevPrevFastMa = _prevFastMa;
			_prevFastMa = fastMaValue;
			_prevSlowMa = slowMaValue;
			return 0;
		}

		var previousFast = _prevFastMa.Value;
		var previousFast2 = _prevPrevFastMa.Value;
		var previousSlow = _prevSlowMa.Value;

		var signal = 0;

		if (previousFast2 < previousSlow && previousFast > previousSlow)
			signal = 1;
		else if (previousFast2 > previousSlow && previousFast < previousSlow)
			signal = -1;

		_prevPrevFastMa = _prevFastMa;
		_prevFastMa = fastMaValue;
		_prevSlowMa = slowMaValue;

		return signal;
	}

	private int UpdateRsiSignal(decimal rsiValue)
	{
		if (!_rsi.IsFormed)
		{
			_prevPrevRsi = _prevRsi;
			_prevRsi = rsiValue;
			return 0;
		}

		if (_prevRsi is null || _prevPrevRsi is null)
		{
			_prevPrevRsi = _prevRsi;
			_prevRsi = rsiValue;
			return 0;
		}

		var previous = _prevRsi.Value;
		var previous2 = _prevPrevRsi.Value;

		var signal = 0;

		if (previous2 < 30m && previous > 30m)
			signal = 1;
		else if (previous2 > 70m && previous < 70m)
			signal = -1;

		_prevPrevRsi = _prevRsi;
		_prevRsi = rsiValue;

		return signal;
	}

	private int UpdateCciSignal(decimal cciValue)
	{
		if (!_cci.IsFormed)
		{
			_prevPrevCci = _prevCci;
			_prevCci = cciValue;
			return 0;
		}

		if (_prevCci is null || _prevPrevCci is null)
		{
			_prevPrevCci = _prevCci;
			_prevCci = cciValue;
			return 0;
		}

		var previous = _prevCci.Value;
		var previous2 = _prevPrevCci.Value;

		var signal = 0;

		if (previous2 < -100m && previous > -100m)
			signal = 1;
		else if (previous2 > 100m && previous < 100m)
			signal = -1;

		_prevPrevCci = _prevCci;
		_prevCci = cciValue;

		return signal;
	}

	private int UpdateSlopeSignal(decimal slopeValue)
	{
		if (!_slopeMa.IsFormed)
		{
			_prevPrevSlopeMa = _prevSlopeMa;
			_prevSlopeMa = slopeValue;
			return 0;
		}

		if (_prevSlopeMa is null || _prevPrevSlopeMa is null)
		{
			_prevPrevSlopeMa = _prevSlopeMa;
			_prevSlopeMa = slopeValue;
			return 0;
		}

		var previous = _prevSlopeMa.Value;
		var previous2 = _prevPrevSlopeMa.Value;

		var signal = 0;

		if (previous > previous2)
			signal = 1;
		else if (previous < previous2)
			signal = -1;

		_prevPrevSlopeMa = _prevSlopeMa;
		_prevSlopeMa = slopeValue;

		return signal;
	}

	private int UpdateAoSignal(decimal aoValue)
	{
		if (!_ao.IsFormed)
		{
			_prevAo = aoValue;
			return 0;
		}

		if (_prevAo is null)
		{
			_prevAo = aoValue;
			return 0;
		}

		var previous = _prevAo.Value;

		var signal = 0;

		if (aoValue > previous)
			signal = 1;
		else if (aoValue < previous)
			signal = -1;

		_prevAo = aoValue;

		return signal;
	}

	private void ResetState()
	{
		_baseWeights = new decimal[5];
		_lastIndicatorSignals = new int[5];
		_lastNeuronOutputs = new decimal[5];
		_indicatorWeights = new decimal[5, 6];

		for (var i = 0; i < _baseWeights.Length; ++i)
			_baseWeights[i] = 1m;

		for (var i = 0; i < 5; ++i)
		{
			foreach (var indicatorIndex in _neuronIndicators[i])
				_indicatorWeights[i, indicatorIndex] = 1m;
		}

		_prevFastMa = null;
		_prevPrevFastMa = null;
		_prevSlowMa = null;
		_prevRsi = null;
		_prevPrevRsi = null;
		_prevCci = null;
		_prevPrevCci = null;
		_prevSlopeMa = null;
		_prevPrevSlopeMa = null;
		_prevAo = null;

		_hasLastSignals = false;
		_lastTradeDirection = 0;
		_entryPrice = 0m;
		_stopLossPrice = 0m;
		_takeProfitPrice = 0m;
		_isLongPosition = false;
		_entryCandleTime = null;
	}

	private void ResetAfterExit()
	{
		_entryPrice = 0m;
		_stopLossPrice = 0m;
		_takeProfitPrice = 0m;
		_isLongPosition = false;
		_entryCandleTime = null;
		_lastTradeDirection = 0;
		_hasLastSignals = false;

		_lastIndicatorSignals = new int[5];
		_lastNeuronOutputs = new decimal[5];
	}
}