Adaptive Perceptron-Strategie
Übersicht
Diese Strategie ist ein StockSharp-Port des MetaTrader 5-Expertenberaters Perceptron.mq5.
Fünf diskrete Indikatorsignale werden durch ein zweischichtiges Perzeptron kombiniert. Jeder Trade zeichnet den Indikatorstatus auf und sobald die Position geschlossen wird, werden synaptische Gewichte je nach erzieltem Gewinn verstärkt oder bestraft. Das Verhalten ahmt die Selbstlernschleife des ursprünglichen EA nach und nutzt dabei die StockSharp-High-Level-Kerzen-API.
Indikatorschicht
| Code | Beschreibung | Signallogik |
|---|---|---|
IND1 |
Schneller/langsamer einfacher gleitender Durchschnitt Crossover | +1 wenn die schnelle MA auf dem vorherigen Bar über die langsame MA kreuzt, −1 bei einem Abwärtskreuz, sonst 0. |
IND2 |
Relative Strength Index (RSI) | +1 wenn RSI die überverkaufte Zone verlässt (kreuzt über 30), −1 wenn RSI die überkaufte Zone verlässt (kreuzt unter 70). |
IND3 |
Commodity Channel Index (CCI) | +1 bei einem Kreuz über −100, −1 bei einem Kreuz unter +100. |
IND4 |
Steigung des kurzen einfachen gleitenden Durchschnitts | +1 wenn die kurze MA zwischen den zwei vorherigen Bars gestiegen ist, −1 wenn sie gesunken ist. |
IND5 |
Awesome Oscillator Momentum-Farbe | +1 wenn das Histogramm im Vergleich zum vorherigen Wert zunimmt (bullishe Farbe), −1 wenn es abnimmt. |
Alle Indikatoren werden auf abgeschlossenen Kerzen ausgewertet. Historische Puffer werden intern gepflegt, um das CopyBuffer-Windowing des MQL5-Skripts zu replizieren.
Perzeptron-Architektur
- Fünf versteckte Neuronen (
NN1…NN5) kombinieren jeweils vier Indikatoren und spiegeln die Verdrahtung im EA wider. - Jedes Neuron hat sein eigenes Wörterbuch synaptischer Gewichte plus ein Bias-Gewicht (
NNS1…NNS5). - Die endgültige Aktivierung
brainReturnist die gewichtete Summe der Neuronenausgaben.brainReturn > 0→ Long-Einstieg anfordern (wenn der vorherige Trade nicht auch Long war).brainReturn < 0→ Short-Einstieg anfordern (wenn der vorherige Trade nicht auch Short war).
- Positionen werden nur mit Marktorders eröffnet, wenn keine Position aktiv ist.
Positionsmanagement
- Einstiegspreis, Richtung und Indikator-/Neuronenstatus werden bei jeder Ausführung erfasst.
- Take-Profit- und Stop-Loss-Versätze werden in absoluten Preiseinheiten angewendet (z.B. 0.0004 für 4 Punkte bei einem Forex-Paar mit 5 Dezimalstellen).
Wenn eine neue Kerze nach dem Einstieg öffnet:- Bei Longs wird zuerst das Hoch mit dem Take-Profit-Preis verglichen, dann das Tief mit dem Stop-Loss.
- Bei Shorts wird zuerst das Tief mit dem Take-Profit-Preis verglichen, dann das Hoch mit dem Stop-Loss.
- Wenn beide Level innerhalb derselben Kerze überschritten werden, hat der Take-Profit Priorität, entsprechend dem optimistischen Verhalten des ursprünglichen EA.
- Sobald ein Ausstieg erkannt wird, schließt die Strategie die Position mit einer Marktorder und berechnet den realisierten Gewinn unter Verwendung des entsprechenden TP/SL-Levels.
Adaptive Gewichtsaktualisierung
Wenn ein Trade schließt, werden die erfassten Indikator- und Neuronenzustände wiedergegeben:
directionSign(−1 für Longs, +1 für Shorts) undoutcomeSign(Vorzeichen des realisierten PnL) werden bestimmt.- Bias-Gewichte werden innerhalb
[SinMin, SinMax]angepasst:- Wenn
sign(neuronOutput) * directionSignpositiv ist, folgt das Bias dem Trade-Ergebnis (Erhöhung bei Gewinn, Reduzierung bei Verlust). - Andernfalls bewegt sich das Bias entgegen dem Ergebnis.
- Wenn
- Synaptische Gewichte verhalten sich ähnlich, bleiben aber unbegrenzt: Mit der Positionsrichtung ausgerichtete Signale erhalten Verstärkung bei Gewinnen und Strafen bei Verlusten, während entgegengesetzte Signale das Inverse tun.
- Gespeicherte Signale werden gelöscht, um versehentliche Wiederverwendung zu vermeiden.
Dies verallgemeinert die 1.500+ Zeilen bedingter Synapsenverwaltung aus dem EA in eine kompakte Verstärkungsroutine.
Parameter
| Parameter | Standard | Beschreibung |
|---|---|---|
CandleType |
1-Minuten-Zeitrahmen | Kerzenabonnement der Strategie. |
FastMaLength |
5 | Periode der schnellen SMA im Crossover-Signal. |
SlowMaLength |
9 | Periode der langsamen SMA. |
RsiLength |
14 | RSI-Berechnungsperiode. |
CciLength |
14 | CCI-Berechnungsperiode. |
SlopeMaLength |
5 | Periode der MA für die Steilheitserkennung. |
AoShortLength |
5 | Kurze Periode des Awesome Oscillators. |
AoLongLength |
34 | Lange Periode des Awesome Oscillators. |
StopLossOffset |
0.001 | Stop-Loss-Abstand in absoluten Preiseinheiten (0 deaktiviert den Stop). |
TakeProfitOffset |
0.0004 | Take-Profit-Abstand in absoluten Preiseinheiten (0 deaktiviert das Ziel). |
SinMax |
5 | Obergrenze für Neuron-Bias-Gewichte. |
SinMin |
0 | Untergrenze für Neuron-Bias-Gewichte. |
SinPlusStep |
0.03 | Positiver Verstärkungsinkrement. |
SinMinusStep |
0.03 | Negativer Verstärkungsdekrement. |
Alle numerischen Parameter sind als StrategyParam<T> exponiert und können im StockSharp Designer optimiert werden.
Implementierungshinweise
- Verwendet die High-Level-Kerzenabonnement-API mit Multi-Indikator-Bindung.
- Manuelle Trade-Verwaltung wird eingesetzt, damit realisierte Preise beim Aktualisieren von Synapsen bekannt sind.
- Indikatoren werden mit nullbaren Feldern gespeichert, damit Signale erst nach vollständiger Ausbildung ausgelöst werden.
- Der Farb-Puffer des Awesome Oscillators im EA wird durch den Vergleich aktueller und vorheriger Histogrammwerte approximiert.
- Die Chart-Ausgabe zeichnet die Kerzenserie plus die schnellen und langsamen gleitenden Durchschnitte. Trade-Marker zeigen das adaptive Verhalten in Echtzeit.
Einschränkungen und Annahmen
- Stops und Ziele werden einmal pro abgeschlossener Kerze ausgewertet; die Intrabar-Reihenfolge der Ereignisse ist unbekannt, daher wird dem Gewinnziel Priorität eingeräumt, wenn beide Schwellen getroffen werden.
- Indikatorgewichte sind wie im ursprünglichen EA unbegrenzt und können während verlängerter Verstärkungszyklen stark anwachsen.
- Der
LastTradeTypedes ursprünglichen EA wurde nie zurückgesetzt; in diesem Port wird er nach jedem Ausstieg geleert, sodass aufeinanderfolgende Trades in dieselbe Richtung möglich bleiben.
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];
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import (
SimpleMovingAverage,
RelativeStrengthIndex,
CommodityChannelIndex,
AwesomeOscillator,
)
class perceptron_adaptive_strategy(Strategy):
"""Adaptive multi-layer perceptron: combines 5 indicator signals, tunes weights after trades."""
# Neuron-to-indicator mapping (each neuron uses 4 of 5 indicators, indices 1-5)
_NEURON_INDICATORS = [
[2, 3, 4, 5],
[1, 3, 4, 5],
[1, 2, 4, 5],
[1, 2, 3, 5],
[1, 2, 3, 4],
]
def __init__(self):
super(perceptron_adaptive_strategy, self).__init__()
self._stop_loss_offset = self.Param("StopLossOffset", 500.0) \
.SetDisplay("Stop Loss Offset", "Stop-loss distance in absolute price units", "Risk Management")
self._take_profit_offset = self.Param("TakeProfitOffset", 300.0) \
.SetDisplay("Take Profit Offset", "Take-profit distance in absolute price units", "Risk Management")
self._sin_max = self.Param("SinMax", 5) \
.SetDisplay("Synapse Upper Bound", "Maximum value for neuron bias weights", "Neural Network")
self._sin_min = self.Param("SinMin", 0) \
.SetDisplay("Synapse Lower Bound", "Minimum value for neuron bias weights", "Neural Network")
self._sin_plus = self.Param("SinPlusStep", 0.03) \
.SetGreaterThanZero() \
.SetDisplay("Positive Adjustment", "Increment applied when trade is favorable", "Neural Network")
self._sin_minus = self.Param("SinMinusStep", 0.03) \
.SetGreaterThanZero() \
.SetDisplay("Negative Adjustment", "Decrement applied when trade is unfavorable", "Neural Network")
self._fast_ma_length = self.Param("FastMaLength", 5) \
.SetGreaterThanZero() \
.SetDisplay("Fast MA Length", "Fast simple moving average length", "Indicators")
self._slow_ma_length = self.Param("SlowMaLength", 9) \
.SetGreaterThanZero() \
.SetDisplay("Slow MA Length", "Slow simple moving average length", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "Relative Strength Index period", "Indicators")
self._cci_length = self.Param("CciLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("CCI Length", "Commodity Channel Index period", "Indicators")
self._slope_ma_length = self.Param("SlopeMaLength", 5) \
.SetGreaterThanZero() \
.SetDisplay("Slope MA Length", "SMA for slope detection", "Indicators")
self._ao_short_length = self.Param("AoShortLength", 5) \
.SetGreaterThanZero() \
.SetDisplay("AO Short Length", "Short period for Awesome Oscillator", "Indicators")
self._ao_long_length = self.Param("AoLongLength", 34) \
.SetGreaterThanZero() \
.SetDisplay("AO Long Length", "Long period for Awesome Oscillator", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe used for calculations", "General")
self._base_weights = [1.0] * 5
# indicator_weights[neuron][indicator_index 0..5]
self._indicator_weights = [[0.0] * 6 for _ in range(5)]
self._last_indicator_signals = [0] * 5
self._last_neuron_outputs = [0.0] * 5
self._prev_fast_ma = None
self._prev_prev_fast_ma = None
self._prev_slow_ma = None
self._prev_rsi = None
self._prev_prev_rsi = None
self._prev_cci = None
self._prev_prev_cci = None
self._prev_slope_ma = None
self._prev_prev_slope_ma = None
self._prev_ao = None
self._has_last_signals = False
self._last_trade_direction = 0
self._entry_price = 0.0
self._stop_loss_price = 0.0
self._take_profit_price = 0.0
self._is_long_position = False
self._entry_candle_time = None
@property
def StopLossOffset(self):
return float(self._stop_loss_offset.Value)
@property
def TakeProfitOffset(self):
return float(self._take_profit_offset.Value)
@property
def SinMax(self):
return int(self._sin_max.Value)
@property
def SinMin(self):
return int(self._sin_min.Value)
@property
def SinPlusStep(self):
return float(self._sin_plus.Value)
@property
def SinMinusStep(self):
return float(self._sin_minus.Value)
@property
def FastMaLength(self):
return int(self._fast_ma_length.Value)
@property
def SlowMaLength(self):
return int(self._slow_ma_length.Value)
@property
def RsiLength(self):
return int(self._rsi_length.Value)
@property
def CciLength(self):
return int(self._cci_length.Value)
@property
def SlopeMaLength(self):
return int(self._slope_ma_length.Value)
@property
def AoShortLength(self):
return int(self._ao_short_length.Value)
@property
def AoLongLength(self):
return int(self._ao_long_length.Value)
@property
def CandleType(self):
return self._candle_type.Value
def _reset_state(self):
self._base_weights = [1.0] * 5
self._indicator_weights = [[0.0] * 6 for _ in range(5)]
for i in range(5):
for idx in self._NEURON_INDICATORS[i]:
self._indicator_weights[i][idx] = 1.0
self._last_indicator_signals = [0] * 5
self._last_neuron_outputs = [0.0] * 5
self._prev_fast_ma = None
self._prev_prev_fast_ma = None
self._prev_slow_ma = None
self._prev_rsi = None
self._prev_prev_rsi = None
self._prev_cci = None
self._prev_prev_cci = None
self._prev_slope_ma = None
self._prev_prev_slope_ma = None
self._prev_ao = None
self._has_last_signals = False
self._last_trade_direction = 0
self._entry_price = 0.0
self._stop_loss_price = 0.0
self._take_profit_price = 0.0
self._is_long_position = False
self._entry_candle_time = None
def OnStarted2(self, time):
super(perceptron_adaptive_strategy, self).OnStarted2(time)
self._reset_state()
self._fast_ma = SimpleMovingAverage()
self._fast_ma.Length = self.FastMaLength
self._slow_ma = SimpleMovingAverage()
self._slow_ma.Length = self.SlowMaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._cci = CommodityChannelIndex()
self._cci.Length = self.CciLength
self._slope_ma = SimpleMovingAverage()
self._slope_ma.Length = self.SlopeMaLength
self._ao = AwesomeOscillator()
self._ao.ShortMa.Length = self.AoShortLength
self._ao.LongMa.Length = self.AoLongLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ma, self._slow_ma, self._rsi, self._cci, self._slope_ma, self._ao, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._fast_ma)
self.DrawIndicator(area, self._slow_ma)
self.DrawOwnTrades(area)
def process_candle(self, candle, fast_ma_val, slow_ma_val, rsi_val, cci_val, slope_ma_val, ao_val):
if candle.State != CandleStates.Finished:
return
fast_ma_v = float(fast_ma_val)
slow_ma_v = float(slow_ma_val)
rsi_v = float(rsi_val)
cci_v = float(cci_val)
slope_v = float(slope_ma_val)
ao_v = float(ao_val)
ma_signal = self._update_ma_signal(fast_ma_v, slow_ma_v)
rsi_signal = self._update_rsi_signal(rsi_v)
cci_signal = self._update_cci_signal(cci_v)
slope_signal = self._update_slope_signal(slope_v)
ao_signal = self._update_ao_signal(ao_v)
self._handle_position_management(candle)
if (not self._fast_ma.IsFormed or not self._slow_ma.IsFormed
or not self._rsi.IsFormed or not self._cci.IsFormed):
return
if self.Position != 0:
return
indicator_signals = [ma_signal, rsi_signal, cci_signal, slope_signal, ao_signal]
neuron_outputs = self._calculate_neuron_outputs(indicator_signals)
brain_return = self._calculate_brain_return(neuron_outputs)
if brain_return > 0 and self._last_trade_direction != 2:
self._open_position(True, float(candle.ClosePrice), candle.OpenTime, indicator_signals, neuron_outputs)
elif brain_return < 0 and self._last_trade_direction != 1:
self._open_position(False, float(candle.ClosePrice), candle.OpenTime, indicator_signals, neuron_outputs)
def _open_position(self, is_long, entry_price, candle_time, indicator_signals, neuron_outputs):
if is_long:
self.BuyMarket()
self._last_trade_direction = 2
else:
self.SellMarket()
self._last_trade_direction = 1
self._entry_price = entry_price
self._is_long_position = is_long
self._entry_candle_time = candle_time
stop_offset = self.StopLossOffset
take_offset = self.TakeProfitOffset
self._stop_loss_price = 0.0
self._take_profit_price = 0.0
if stop_offset > 0:
self._stop_loss_price = entry_price - stop_offset if is_long else entry_price + stop_offset
if take_offset > 0:
self._take_profit_price = entry_price + take_offset if is_long else entry_price - take_offset
self._has_last_signals = True
for i in range(len(indicator_signals)):
self._last_indicator_signals[i] = indicator_signals[i]
for i in range(len(neuron_outputs)):
self._last_neuron_outputs[i] = neuron_outputs[i]
def _handle_position_management(self, candle):
if self.Position == 0 or self._entry_candle_time is None:
return
if candle.OpenTime <= self._entry_candle_time:
return
has_exit = False
exit_price = 0.0
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self._is_long_position:
if self._take_profit_price > 0 and h >= self._take_profit_price:
exit_price = self._take_profit_price
has_exit = True
elif self._stop_loss_price > 0 and lo <= self._stop_loss_price:
exit_price = self._stop_loss_price
has_exit = True
else:
if self._take_profit_price > 0 and lo <= self._take_profit_price:
exit_price = self._take_profit_price
has_exit = True
elif self._stop_loss_price > 0 and h >= self._stop_loss_price:
exit_price = self._stop_loss_price
has_exit = True
if not has_exit:
return
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
profit = exit_price - self._entry_price if self._is_long_position else self._entry_price - exit_price
if self._has_last_signals:
self._adjust_weights(self._is_long_position, profit)
self._reset_after_exit()
def _adjust_weights(self, was_long, profit):
if profit > 0:
outcome_sign = 1
elif profit < 0:
outcome_sign = -1
else:
return
direction_sign = -1 if was_long else 1
sin_plus = self.SinPlusStep
sin_minus = self.SinMinusStep
sin_max = float(self.SinMax)
sin_min = float(self.SinMin)
for ni in range(5):
last_output = self._last_neuron_outputs[ni]
if last_output > 0:
neuron_sign = 1
elif last_output < 0:
neuron_sign = -1
else:
neuron_sign = 0
if neuron_sign != 0:
product = neuron_sign * direction_sign
if product > 0:
if outcome_sign > 0:
self._base_weights[ni] = min(self._base_weights[ni] + sin_plus, sin_max)
else:
self._base_weights[ni] = max(self._base_weights[ni] - sin_minus, sin_min)
elif product < 0:
if outcome_sign > 0:
self._base_weights[ni] = max(self._base_weights[ni] - sin_minus, sin_min)
else:
self._base_weights[ni] = min(self._base_weights[ni] + sin_plus, sin_max)
for ind_idx in self._NEURON_INDICATORS[ni]:
ind_signal = self._last_indicator_signals[ind_idx - 1]
if ind_signal == 0:
continue
product = ind_signal * direction_sign
if product > 0:
self._indicator_weights[ni][ind_idx] += sin_plus if outcome_sign > 0 else -sin_minus
elif product < 0:
self._indicator_weights[ni][ind_idx] += -sin_minus if outcome_sign > 0 else sin_plus
def _calculate_neuron_outputs(self, indicator_signals):
outputs = [0.0] * 5
for ni in range(5):
s = 0.0
for ind_idx in self._NEURON_INDICATORS[ni]:
sig = indicator_signals[ind_idx - 1]
if sig == 0:
continue
w = self._indicator_weights[ni][ind_idx]
s += w * sig
outputs[ni] = s
return outputs
def _calculate_brain_return(self, neuron_outputs):
total = 0.0
for i in range(len(neuron_outputs)):
total += neuron_outputs[i] * self._base_weights[i]
return total
def _update_ma_signal(self, fast_val, slow_val):
if not self._fast_ma.IsFormed or not self._slow_ma.IsFormed:
self._prev_prev_fast_ma = self._prev_fast_ma
self._prev_fast_ma = fast_val
self._prev_slow_ma = slow_val
return 0
if self._prev_fast_ma is None or self._prev_prev_fast_ma is None or self._prev_slow_ma is None:
self._prev_prev_fast_ma = self._prev_fast_ma
self._prev_fast_ma = fast_val
self._prev_slow_ma = slow_val
return 0
prev_f = self._prev_fast_ma
prev_f2 = self._prev_prev_fast_ma
prev_s = self._prev_slow_ma
signal = 0
if prev_f2 < prev_s and prev_f > prev_s:
signal = 1
elif prev_f2 > prev_s and prev_f < prev_s:
signal = -1
self._prev_prev_fast_ma = self._prev_fast_ma
self._prev_fast_ma = fast_val
self._prev_slow_ma = slow_val
return signal
def _update_rsi_signal(self, rsi_val):
if not self._rsi.IsFormed:
self._prev_prev_rsi = self._prev_rsi
self._prev_rsi = rsi_val
return 0
if self._prev_rsi is None or self._prev_prev_rsi is None:
self._prev_prev_rsi = self._prev_rsi
self._prev_rsi = rsi_val
return 0
prev = self._prev_rsi
prev2 = self._prev_prev_rsi
signal = 0
if prev2 < 30 and prev > 30:
signal = 1
elif prev2 > 70 and prev < 70:
signal = -1
self._prev_prev_rsi = self._prev_rsi
self._prev_rsi = rsi_val
return signal
def _update_cci_signal(self, cci_val):
if not self._cci.IsFormed:
self._prev_prev_cci = self._prev_cci
self._prev_cci = cci_val
return 0
if self._prev_cci is None or self._prev_prev_cci is None:
self._prev_prev_cci = self._prev_cci
self._prev_cci = cci_val
return 0
prev = self._prev_cci
prev2 = self._prev_prev_cci
signal = 0
if prev2 < -100 and prev > -100:
signal = 1
elif prev2 > 100 and prev < 100:
signal = -1
self._prev_prev_cci = self._prev_cci
self._prev_cci = cci_val
return signal
def _update_slope_signal(self, slope_val):
if not self._slope_ma.IsFormed:
self._prev_prev_slope_ma = self._prev_slope_ma
self._prev_slope_ma = slope_val
return 0
if self._prev_slope_ma is None or self._prev_prev_slope_ma is None:
self._prev_prev_slope_ma = self._prev_slope_ma
self._prev_slope_ma = slope_val
return 0
prev = self._prev_slope_ma
prev2 = self._prev_prev_slope_ma
signal = 0
if prev > prev2:
signal = 1
elif prev < prev2:
signal = -1
self._prev_prev_slope_ma = self._prev_slope_ma
self._prev_slope_ma = slope_val
return signal
def _update_ao_signal(self, ao_val):
if not self._ao.IsFormed:
self._prev_ao = ao_val
return 0
if self._prev_ao is None:
self._prev_ao = ao_val
return 0
prev = self._prev_ao
signal = 0
if ao_val > prev:
signal = 1
elif ao_val < prev:
signal = -1
self._prev_ao = ao_val
return signal
def _reset_after_exit(self):
self._entry_price = 0.0
self._stop_loss_price = 0.0
self._take_profit_price = 0.0
self._is_long_position = False
self._entry_candle_time = None
self._last_trade_direction = 0
self._has_last_signals = False
self._last_indicator_signals = [0] * 5
self._last_neuron_outputs = [0.0] * 5
def OnReseted(self):
super(perceptron_adaptive_strategy, self).OnReseted()
self._reset_state()
def CreateClone(self):
return perceptron_adaptive_strategy()