GitHub で見る

Perceptron Adaptive Strategy

Overview

This strategy is a StockSharp port of the MetaTrader 5 expert advisor Perceptron.mq5.
Five discrete indicator signals are combined through a two-layer perceptron. Each trade records the indicator state and, once the position is closed, synaptic weights are reinforced or penalized depending on the achieved profit. The behaviour mimics the self-learning loop of the original EA while leveraging the StockSharp high-level candle API.

Indicator layer

Code Description Signal logic
IND1 Fast/slow simple moving average crossover +1 when the fast MA crosses above the slow MA on the previous bar, −1 on a downward cross, otherwise 0.
IND2 Relative Strength Index (RSI) +1 when RSI leaves the oversold area (crosses above 30), −1 when RSI leaves the overbought area (crosses below 70).
IND3 Commodity Channel Index (CCI) +1 on a cross above −100, −1 on a cross below +100.
IND4 Short simple moving average slope +1 if the short MA increased between the two previous bars, −1 if it decreased.
IND5 Awesome Oscillator momentum colour +1 when the histogram increases compared with the previous value (bullish colour), −1 when it decreases.

All indicators are evaluated on closed candles. Historical buffers are maintained internally to replicate the CopyBuffer windowing used in the MQL5 script.

Perceptron architecture

  • Five hidden neurons (NN1NN5) combine four indicators each, mirroring the wiring in the EA.
  • Each neuron has its own dictionary of synaptic weights plus a bias weight (NNS1NNS5).
  • The final activation brainReturn is the weighted sum of neuron outputs.
    • brainReturn > 0 → request a long entry (if the previous trade was not also long).
    • brainReturn < 0 → request a short entry (if the previous trade was not also short).
  • Positions are opened with market orders only when no position is active.

Position management

  • Entry price, direction and indicator/neuron states are captured on every fill.
  • Take-profit and stop-loss offsets are applied in absolute price units (e.g. 0.0004 for 4 points on a Forex pair with 5 decimals).
    When a new candle opens after the entry:
    • For longs the high is compared with the take-profit price first, then the low with the stop-loss.
    • For shorts the low is compared with the take-profit price first, then the high with the stop-loss.
    • If both levels are exceeded inside the same candle the take-profit has priority, matching the optimistic behaviour of the original EA.
  • Once an exit is detected the strategy closes the position with a market order and computes the realised profit using the corresponding TP/SL level.

Adaptive weight update

When a trade closes, the captured indicator and neuron signs are replayed:

  1. Determine directionSign (−1 for longs, +1 for shorts) and outcomeSign (sign of realised PnL).
  2. Bias weights are adjusted inside [SinMin, SinMax]:
    • If sign(neuronOutput) * directionSign is positive the bias follows the trade outcome (increase on profit, decrease on loss).
    • Otherwise the bias moves opposite to the outcome.
  3. Synaptic weights behave similarly but remain unbounded: signals aligned with the position direction receive reinforcement on profits and penalties on losses, while opposing signals do the inverse.
  4. Stored signals are cleared to avoid accidental re-use.

This generalises the 1,500+ lines of conditional synapse management from the EA into a compact reinforcement routine.

Parameters

Parameter Default Description
CandleType 1-minute time frame Candle subscription used by the strategy.
FastMaLength 5 Period of the fast SMA in the crossover signal.
SlowMaLength 9 Period of the slow SMA.
RsiLength 14 RSI calculation period.
CciLength 14 CCI calculation period.
SlopeMaLength 5 Period of the MA used for slope detection.
AoShortLength 5 Short period of the Awesome Oscillator.
AoLongLength 34 Long period of the Awesome Oscillator.
StopLossOffset 0.001 Stop-loss distance in absolute price units (0 disables the stop).
TakeProfitOffset 0.0004 Take-profit distance in absolute price units (0 disables the target).
SinMax 5 Upper bound for neuron bias weights.
SinMin 0 Lower bound for neuron bias weights.
SinPlusStep 0.03 Positive reinforcement increment.
SinMinusStep 0.03 Negative reinforcement decrement.

All numeric parameters are exposed as StrategyParam<T> and can be optimised inside StockSharp Designer.

Implementation notes

  • Uses the high-level candle subscription API with multi-indicator binding.
  • Manual trade management is employed so that realised prices are known when updating synapses.
  • Indicator histories are stored with nullable fields to ensure signals only fire after full formation.
  • The Awesome Oscillator colour buffer in the EA is approximated by comparing current and previous histogram values.
  • Chart output draws the candle series plus the fast and slow moving averages. Trade markers show adaptive behaviour in real time.

Limitations and assumptions

  • Stops and targets are evaluated once per finished candle; intra-bar order of events is unknown, so priority is given to the profit target when both thresholds are hit.
  • Indicator weights are unbounded like in the original EA and may grow large during prolonged reinforcement cycles.
  • The original EA’s LastTradeType never reset; in this port it is cleared after every exit so that consecutive trades in the same direction remain possible.
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];
	}
}