Selbstoptimierender RSI- oder MFI-Trader v3
Übersicht
Diese Strategie portiert den MetaTrader Expert Advisor "Self Optimizing RSI or MFI Trader" auf die High-Level-API von StockSharp. Bei jeder fertigen Kerze testet der Algorithmus ein gleitendes Fenster historischer Balken und findet die profitabelsten Überkauf- und Überverkauf-Schwellenwerte für den gewählten Oszillator. Live-Trades werden nur eingegangen, wenn der aktuelle Oszillatorwert den besten Schwellenwert in der gleichen Richtung wie der historische Vorteil kreuzt, optional ohne eine Kreuzung im "aggressiven" Modus zu erfordern. Positionsausstiege basieren auf ATR-basierten oder Festabstand-Stops und -Zielen mit einem optionalen Breakeven-Schritt.
Marktdaten
- Funktioniert mit jedem Instrument, das OHLC-Kerzen und Volumen bereitstellt (MFI benötigt Volumen).
- Verwendet den durch den
CandleType-Parameter angegebenen Zeitrahmen. Standard sind 15-Minuten-Kerzen, aber Sie können jeden vom Venue-Adapter unterstützten Zeitrahmen anhängen.
Indikatoren
- Relative Strength Index (RSI) oder Money Flow Index (MFI) abhängig vom
IndicatorChoice-Parameter. Beide teilen die gleiche Durchschnittslänge. - Average True Range (ATR) für ATR-basierte Stop-Loss-/Take-Profit-Dimensionierung wenn
UseDynamicTargetsaktiviert ist.
Handelslogik
- Eine rollende Historie von
OptimizingPeriods+ 1 fertigen Kerzen mit ihren Oszillatorwerten und Schlusspreisen pflegen. - Für jedes ganzzahlige Level zwischen
IndicatorBottomValueundIndicatorTopValuesimuliert die Strategie Trades im historischen Fenster:- Short-Simulation: zählen wie oft der Oszillator unter das Level gekreuzt ist und ob ein Short-Stop-Loss oder Take-Profit zuerst getroffen worden wäre.
- Long-Simulation: zählen wie oft der Oszillator über das Level gekreuzt ist und wie profitabel die Trades gewesen wären.
- Den Schwellenwert wählen, der die höchste simulierte Profitabilität für jede Richtung geliefert hat. Wenn
TradeReverseaktiviert ist, werden die Profitabilitätspunkte vertauscht, so dass die entgegengesetzte Richtung bevorzugt wird. - Wenn der Live-Oszillator das beste Level in der profitablen Richtung kreuzt (oder sofort wenn
UseAggressiveEntrieswahr ist) öffnet die Strategie eine Position unter Berücksichtigung vonOneOrderAtATime. - Ausstiegsmanagement:
- Stop-Loss- und Take-Profit-Level werden entweder aus ATR-Vielfachen (
StopLossAtrMultiplier,TakeProfitAtrMultiplier) oder aus Festpunkt-Abständen (StaticStopLossPoints,StaticTakeProfitPoints) berechnet. UseBreakEvenverschiebt den Stop auf den Einstiegspreis plusBreakEvenPaddingPoints, sobald der unrealisierte GewinnBreakEvenTriggerPointserreicht.- Positionen werden geschlossen, wenn entweder Stop-Loss- oder Take-Profit-Preise gekreuzt werden.
- Stop-Loss- und Take-Profit-Level werden entweder aus ATR-Vielfachen (
Risikomanagement
- Dynamische Dimensionierung: wenn
UseDynamicVolumewahr ist, riskiert die StrategieRiskPercentdes aktuellen Portfoliowerts. Die Berechnung konvertiert den Stop-Abstand in monetäres Risiko unter Verwendung vonPriceStepundStepPricedes Wertpapiers. - Statische Dimensionierung: wenn deaktiviert, werden
BaseVolumeLots bei jedem Einstieg gehandelt. - Breakeven-Schutz: stellt sicher, dass gewinnende Trades geschützt werden, sobald ausreichend Gewinn aufgelaufen ist.
Parameter
| Parameter | Beschreibung |
|---|---|
OptimizingPeriods |
Anzahl der Balken für die rollende In-Sample-Optimierung (Standard 144). |
IndicatorChoice |
Wählt RSI oder MFI als treibenden Oszillator. |
IndicatorPeriod |
Durchschnittszeitraum für Oszillator und ATR. |
IndicatorTopValue / IndicatorBottomValue |
Suchgrenzen für Schwellenwert-Level (typischerweise 0–100). |
UseAggressiveEntries |
Wenn wahr, erlaubt Einstiege ohne bestätigte Kreuzung. |
TradeReverse |
Tauscht Profitabilitätspunkte um, um die historisch verlierende Seite zu handeln. |
OneOrderAtATime |
Verhindert das Öffnen einer neuen Position während eine andere aktiv ist. |
UseDynamicTargets |
Wechselt zwischen ATR-basierten und Festpunkt-Stops/-Zielen. |
StopLossAtrMultiplier, TakeProfitAtrMultiplier |
ATR-Multiplikatoren für dynamische Ausstiege. |
StaticStopLossPoints, StaticTakeProfitPoints |
Punktabstände für feste Ausstiege. |
UseBreakEven, BreakEvenTriggerPoints, BreakEvenPaddingPoints |
Konfiguriert das Breakeven-Stop-Verhalten. |
UseDynamicVolume, RiskPercent, BaseVolume |
Steuert die Positionsgrößenlogik. |
CandleType |
Zeitrahmen für Optimierung und Handel. |
Implementierungshinweise
- Die Strategie verwendet die
SubscribeCandles().Bind(...)-Pipeline von StockSharp, läuft also nur auf abgeschlossenen Kerzen. OneOrderAtATimesollte beim Handel in einem Netting-Konto aktiviert bleiben, da die Implementierung eine einzelne aggregierte Position verfolgt.- ATR-basierte Ausstiege erfordern einen gültigen ATR-Wert; die Strategie überspringt den Handel, bis der Indikator vollständig gebildet ist.
- Bei Verwendung von MFI sicherstellen, dass der Datenfeed Volumen liefert, sonst gibt der Indikator null zurück und es werden keine Trades generiert.
Optimierungstipps
OptimizingPeriods, Oszillator-Periode und ATR-Multiplikatoren gemeinsam optimieren, um das Volatilitätsregime des Instruments anzupassen.- Verschiedene Assets können von engeren Level-Bereichen profitieren (z.B. 20–80) um Rauschen zu reduzieren.
- Walk-Forward-Analyse für Vorwärtstests erwägen, da die Strategie Schwellenwerte kontinuierlich anpasst.
Verwendung
- Strategie einem Connector im Designer hinzufügen oder programmatisch ausführen.
- Gewünschtes Wertpapier, Portfolio und Parameterwerte setzen.
- Strategie starten; sie beginnt mit dem Handel sobald genug Kerzen für die Optimierung angesammelt sind.
Einschränkungen
- Historische Optimierung erfolgt auf jedem Balken und kann bei sehr großen
OptimizingPeriodsoder breiten Level-Bereichen CPU-intensiv sein. - Da Level ganzzahlig sind, werden feinkörnige Schwellenwerte (z.B. 70.5) nicht getestet.
- Der Ansatz setzt voraus, dass die jüngste Vergangenheit prädiktiv bleibt; plötzliche Regimewechsel können die Leistung beeinträchtigen, also Live-Ergebnisse überwachen und Konfiguration bei Bedarf anpassen.
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;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that dynamically optimizes RSI or MFI threshold levels over a rolling history window.
/// Chooses the most profitable overbought/oversold levels and executes trades with ATR or point based risk control.
/// </summary>
public class SelfOptimizingRsiOrMfiTraderV3Strategy : Strategy
{
private readonly StrategyParam<int> _optimizingPeriods;
private readonly StrategyParam<bool> _useAggressiveEntries;
private readonly StrategyParam<bool> _tradeReverse;
private readonly StrategyParam<bool> _oneOrderAtATime;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<bool> _useDynamicVolume;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<IndicatorSources> _indicatorChoice;
private readonly StrategyParam<int> _indicatorTopValue;
private readonly StrategyParam<int> _indicatorBottomValue;
private readonly StrategyParam<int> _indicatorPeriod;
private readonly StrategyParam<bool> _useDynamicTargets;
private readonly StrategyParam<int> _staticStopLossPoints;
private readonly StrategyParam<int> _staticTakeProfitPoints;
private readonly StrategyParam<decimal> _stopLossAtrMultiplier;
private readonly StrategyParam<decimal> _takeProfitAtrMultiplier;
private readonly StrategyParam<bool> _useBreakEven;
private readonly StrategyParam<int> _breakEvenTriggerPoints;
private readonly StrategyParam<int> _breakEvenPaddingPoints;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal indicator, decimal close)> _history = new();
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takeProfitPrice;
private IIndicator _indicator;
private AverageTrueRange _atr;
/// <summary>
/// Indicator source used for optimization.
/// </summary>
public enum IndicatorSources
{
/// <summary>
/// Use Relative Strength Index values.
/// </summary>
RelativeStrengthIndex,
/// <summary>
/// Use Money Flow Index values.
/// </summary>
MoneyFlowIndex,
}
/// <summary>
/// Number of bars evaluated when searching for best thresholds.
/// </summary>
public int OptimizingPeriods
{
get => _optimizingPeriods.Value;
set => _optimizingPeriods.Value = value;
}
/// <summary>
/// Allow entries without waiting for indicator crosses.
/// </summary>
public bool UseAggressiveEntries
{
get => _useAggressiveEntries.Value;
set => _useAggressiveEntries.Value = value;
}
/// <summary>
/// Invert profitability preference to trade opposite direction.
/// </summary>
public bool TradeReverse
{
get => _tradeReverse.Value;
set => _tradeReverse.Value = value;
}
/// <summary>
/// Restrict strategy to a single open position at a time.
/// </summary>
public bool OneOrderAtATime
{
get => _oneOrderAtATime.Value;
set => _oneOrderAtATime.Value = value;
}
/// <summary>
/// Static volume used when dynamic sizing is disabled.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Enable risk based position sizing.
/// </summary>
public bool UseDynamicVolume
{
get => _useDynamicVolume.Value;
set => _useDynamicVolume.Value = value;
}
/// <summary>
/// Percentage of portfolio risked per trade when sizing dynamically.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Oscillator used for optimization.
/// </summary>
public IndicatorSources IndicatorChoice
{
get => _indicatorChoice.Value;
set => _indicatorChoice.Value = value;
}
/// <summary>
/// Highest threshold tested when searching for overbought levels.
/// </summary>
public int IndicatorTopValue
{
get => _indicatorTopValue.Value;
set => _indicatorTopValue.Value = value;
}
/// <summary>
/// Lowest threshold tested when searching for oversold levels.
/// </summary>
public int IndicatorBottomValue
{
get => _indicatorBottomValue.Value;
set => _indicatorBottomValue.Value = value;
}
/// <summary>
/// Period used for the selected indicator.
/// </summary>
public int IndicatorPeriod
{
get => _indicatorPeriod.Value;
set => _indicatorPeriod.Value = value;
}
/// <summary>
/// Enable ATR based stop-loss and take-profit levels.
/// </summary>
public bool UseDynamicTargets
{
get => _useDynamicTargets.Value;
set => _useDynamicTargets.Value = value;
}
/// <summary>
/// Static stop-loss distance expressed in points when dynamic targets are disabled.
/// </summary>
public int StaticStopLossPoints
{
get => _staticStopLossPoints.Value;
set => _staticStopLossPoints.Value = value;
}
/// <summary>
/// Static take-profit distance expressed in points when dynamic targets are disabled.
/// </summary>
public int StaticTakeProfitPoints
{
get => _staticTakeProfitPoints.Value;
set => _staticTakeProfitPoints.Value = value;
}
/// <summary>
/// ATR multiplier applied to stop-loss when dynamic targets are enabled.
/// </summary>
public decimal StopLossAtrMultiplier
{
get => _stopLossAtrMultiplier.Value;
set => _stopLossAtrMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier applied to take-profit when dynamic targets are enabled.
/// </summary>
public decimal TakeProfitAtrMultiplier
{
get => _takeProfitAtrMultiplier.Value;
set => _takeProfitAtrMultiplier.Value = value;
}
/// <summary>
/// Enable stop adjustment to breakeven once profit target is reached.
/// </summary>
public bool UseBreakEven
{
get => _useBreakEven.Value;
set => _useBreakEven.Value = value;
}
/// <summary>
/// Profit threshold in points required to arm the breakeven stop.
/// </summary>
public int BreakEvenTriggerPoints
{
get => _breakEvenTriggerPoints.Value;
set => _breakEvenTriggerPoints.Value = value;
}
/// <summary>
/// Additional padding in points applied once breakeven triggers.
/// </summary>
public int BreakEvenPaddingPoints
{
get => _breakEvenPaddingPoints.Value;
set => _breakEvenPaddingPoints.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="SelfOptimizingRsiOrMfiTraderV3Strategy"/>.
/// </summary>
public SelfOptimizingRsiOrMfiTraderV3Strategy()
{
_optimizingPeriods = Param(nameof(OptimizingPeriods), 30)
.SetGreaterThanZero()
.SetDisplay("Optimization Bars", "Number of bars used for optimization", "General")
.SetOptimize(20, 100, 10);
_useAggressiveEntries = Param(nameof(UseAggressiveEntries), false)
.SetDisplay("Aggressive Entries", "Allow entries without indicator crosses", "Trading");
_tradeReverse = Param(nameof(TradeReverse), false)
.SetDisplay("Reverse Trading", "Swap profitability preference for opposite trades", "Trading");
_oneOrderAtATime = Param(nameof(OneOrderAtATime), true)
.SetDisplay("One Position", "Permit only one open position", "Trading");
_baseVolume = Param(nameof(BaseVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Base Volume", "Static order volume when sizing manually", "Risk");
_useDynamicVolume = Param(nameof(UseDynamicVolume), true)
.SetDisplay("Dynamic Volume", "Use risk percentage for position sizing", "Risk");
_riskPercent = Param(nameof(RiskPercent), 2m)
.SetRange(0.1m, 10m)
.SetDisplay("Risk %", "Percent of capital risked per trade", "Risk");
_indicatorChoice = Param(nameof(IndicatorChoice), IndicatorSources.RelativeStrengthIndex)
.SetDisplay("Indicator", "Oscillator optimized by the strategy", "Indicator");
_indicatorTopValue = Param(nameof(IndicatorTopValue), 100)
.SetDisplay("Top Level", "Upper bound for level search", "Indicator");
_indicatorBottomValue = Param(nameof(IndicatorBottomValue), 0)
.SetDisplay("Bottom Level", "Lower bound for level search", "Indicator");
_indicatorPeriod = Param(nameof(IndicatorPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Indicator Period", "Averaging period for RSI or MFI", "Indicator");
_useDynamicTargets = Param(nameof(UseDynamicTargets), true)
.SetDisplay("Dynamic Targets", "Use ATR based stop-loss and take-profit", "Risk");
_staticStopLossPoints = Param(nameof(StaticStopLossPoints), 1000)
.SetGreaterThanZero()
.SetDisplay("Static Stop", "Stop-loss in points when dynamic targets disabled", "Risk");
_staticTakeProfitPoints = Param(nameof(StaticTakeProfitPoints), 2000)
.SetGreaterThanZero()
.SetDisplay("Static Take", "Take-profit in points when dynamic targets disabled", "Risk");
_stopLossAtrMultiplier = Param(nameof(StopLossAtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Stop Mult", "Stop-loss multiplier applied to ATR", "Risk");
_takeProfitAtrMultiplier = Param(nameof(TakeProfitAtrMultiplier), 7m)
.SetGreaterThanZero()
.SetDisplay("ATR Take Mult", "Take-profit multiplier applied to ATR", "Risk");
_useBreakEven = Param(nameof(UseBreakEven), true)
.SetDisplay("Use Breakeven", "Move stop to breakeven after trigger", "Risk");
_breakEvenTriggerPoints = Param(nameof(BreakEvenTriggerPoints), 200)
.SetGreaterThanZero()
.SetDisplay("Breakeven Trigger", "Profit in points required to arm breakeven", "Risk");
_breakEvenPaddingPoints = Param(nameof(BreakEvenPaddingPoints), 100)
.SetGreaterThanZero()
.SetDisplay("Breakeven Padding", "Padding in points applied after trigger", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for analysis", "General");
Volume = 1m;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_entryPrice = null;
_stopPrice = null;
_takeProfitPrice = null;
_indicator = null;
_atr = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_indicator = IndicatorChoice switch
{
IndicatorSources.MoneyFlowIndex => new MoneyFlowIndex { Length = IndicatorPeriod },
_ => new RelativeStrengthIndex { Length = IndicatorPeriod }
};
_atr = new AverageTrueRange { Length = IndicatorPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_indicator, _atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _indicator);
DrawIndicator(area, _atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal indicatorValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
_history.Add((indicatorValue, candle.ClosePrice));
var maxNeeded = Math.Max(OptimizingPeriods + 1, 3);
while (_history.Count > maxNeeded)
{
_history.RemoveAt(0);
}
var priceStep = Security?.PriceStep ?? 1m;
if (priceStep <= 0m)
priceStep = 1m;
var stepPrice = priceStep;
var triggerDiff = UseBreakEven ? BreakEvenTriggerPoints * priceStep : 0m;
var paddingPoints = BreakEvenPaddingPoints > BreakEvenTriggerPoints ? 0 : BreakEvenPaddingPoints;
var paddingDiff = UseBreakEven ? paddingPoints * priceStep : 0m;
ManageOpenPosition(candle, triggerDiff, paddingDiff);
if (_history.Count < maxNeeded)
return;
var indicatorValues = new decimal[_history.Count];
var closeValues = new decimal[_history.Count];
for (var i = 0; i < _history.Count; i++)
{
var source = _history[_history.Count - 1 - i];
indicatorValues[i] = source.indicator;
closeValues[i] = source.close;
}
decimal stopLossDiff;
decimal takeProfitDiff;
if (UseDynamicTargets)
{
if (atrValue <= 0m)
return;
stopLossDiff = atrValue * StopLossAtrMultiplier;
takeProfitDiff = atrValue * TakeProfitAtrMultiplier;
}
else
{
stopLossDiff = StaticStopLossPoints * priceStep;
takeProfitDiff = StaticTakeProfitPoints * priceStep;
}
if (stopLossDiff <= 0m || takeProfitDiff <= 0m)
return;
var volume = CalculateVolume(stopLossDiff);
if (volume <= 0m)
return;
var stepMultiplier = priceStep > 0m ? stepPrice / priceStep : 1m;
var (sellLevel, sellProfit) = CalculateBestSellLevel(indicatorValues, closeValues, stopLossDiff, takeProfitDiff, volume, stepMultiplier);
var (buyLevel, buyProfit) = CalculateBestBuyLevel(indicatorValues, closeValues, stopLossDiff, takeProfitDiff, volume, stepMultiplier);
var adjustedSellProfit = sellProfit;
var adjustedBuyProfit = buyProfit;
if (TradeReverse)
{
adjustedSellProfit = buyProfit;
adjustedBuyProfit = sellProfit;
}
var canEnter = !OneOrderAtATime || Position == 0m;
var currentIndicator = indicatorValues[0];
var previousIndicator = indicatorValues[1];
if (adjustedSellProfit > adjustedBuyProfit)
{
if (canEnter && ((currentIndicator < sellLevel && previousIndicator > sellLevel) || UseAggressiveEntries))
{
EnterShort(candle, volume, stopLossDiff, takeProfitDiff);
}
}
else if (adjustedSellProfit < adjustedBuyProfit)
{
if (canEnter && ((currentIndicator > buyLevel && previousIndicator < buyLevel) || UseAggressiveEntries))
{
EnterLong(candle, volume, stopLossDiff, takeProfitDiff);
}
}
}
private (int level, decimal profit) CalculateBestSellLevel(decimal[] indicatorValues, decimal[] closeValues, decimal stopLossDiff, decimal takeProfitDiff, decimal volume, decimal stepMultiplier)
{
var bottom = Math.Min(IndicatorBottomValue, IndicatorTopValue);
var top = Math.Max(IndicatorBottomValue, IndicatorTopValue);
var bestProfit = 0m;
var bestLevel = bottom;
var updated = false;
for (var level = bottom; level <= top; level++)
{
var profit = EvaluateSellLevel(indicatorValues, closeValues, level, stopLossDiff, takeProfitDiff, volume, stepMultiplier);
if (profit > bestProfit)
{
bestProfit = profit;
bestLevel = level;
updated = true;
}
}
return (bestLevel, updated ? bestProfit : 0m);
}
private (int level, decimal profit) CalculateBestBuyLevel(decimal[] indicatorValues, decimal[] closeValues, decimal stopLossDiff, decimal takeProfitDiff, decimal volume, decimal stepMultiplier)
{
var bottom = Math.Min(IndicatorBottomValue, IndicatorTopValue);
var top = Math.Max(IndicatorBottomValue, IndicatorTopValue);
var bestProfit = 0m;
var bestLevel = top;
var updated = false;
for (var level = top; level >= bottom; level--)
{
var profit = EvaluateBuyLevel(indicatorValues, closeValues, level, stopLossDiff, takeProfitDiff, volume, stepMultiplier);
if (profit > bestProfit)
{
bestProfit = profit;
bestLevel = level;
updated = true;
}
}
return (bestLevel, updated ? bestProfit : 0m);
}
private decimal EvaluateSellLevel(decimal[] indicatorValues, decimal[] closeValues, int level, decimal stopLossDiff, decimal takeProfitDiff, decimal volume, decimal stepMultiplier)
{
var totalProfit = 0m;
if (indicatorValues.Length < 3)
return 0m;
var threshold = (decimal)level;
for (var i = indicatorValues.Length - 2; i >= 2; i--)
{
if (indicatorValues[i] < threshold && indicatorValues[i + 1] > threshold)
{
var entryPrice = closeValues[i];
for (var j = i - 1; j >= 1; j--)
{
var price = closeValues[j];
if (price >= entryPrice + stopLossDiff)
{
var loss = (price - entryPrice) * stepMultiplier * volume;
totalProfit -= loss;
i = j;
break;
}
if (price <= entryPrice - takeProfitDiff)
{
var gain = (entryPrice - price) * stepMultiplier * volume;
totalProfit += gain;
i = j;
break;
}
}
}
}
return totalProfit;
}
private decimal EvaluateBuyLevel(decimal[] indicatorValues, decimal[] closeValues, int level, decimal stopLossDiff, decimal takeProfitDiff, decimal volume, decimal stepMultiplier)
{
var totalProfit = 0m;
if (indicatorValues.Length < 3)
return 0m;
var threshold = (decimal)level;
for (var i = indicatorValues.Length - 2; i >= 2; i--)
{
if (indicatorValues[i] > threshold && indicatorValues[i + 1] < threshold)
{
var entryPrice = closeValues[i];
for (var j = i - 1; j >= 1; j--)
{
var price = closeValues[j];
if (price <= entryPrice - stopLossDiff)
{
var loss = (entryPrice - price) * stepMultiplier * volume;
totalProfit -= loss;
i = j;
break;
}
if (price >= entryPrice + takeProfitDiff)
{
var gain = (price - entryPrice) * stepMultiplier * volume;
totalProfit += gain;
i = j;
break;
}
}
}
}
return totalProfit;
}
private decimal CalculateVolume(decimal stopLossDiff)
{
var volume = BaseVolume;
if (UseDynamicVolume && stopLossDiff > 0m && Security != null)
{
var priceStep = Security.PriceStep ?? 0m;
var stepPrice = priceStep;
if (priceStep > 0m && stepPrice > 0m)
{
var stopPoints = stopLossDiff / priceStep;
var riskPerUnit = stopPoints * stepPrice;
var capital = Portfolio?.CurrentValue ?? 0m;
var riskBudget = capital * (RiskPercent / 100m);
if (riskPerUnit > 0m && riskBudget > 0m)
{
var rawVolume = riskBudget / riskPerUnit;
if (rawVolume > 0m)
volume = rawVolume;
}
}
}
return AdjustVolume(volume);
}
private decimal AdjustVolume(decimal volume)
{
if (Security == null)
return Math.Max(volume, 0.01m);
var step = Security.VolumeStep ?? 0m;
var min = Security.MinVolume ?? 0m;
var max = Security.MaxVolume ?? decimal.MaxValue;
if (step <= 0m)
step = 1m;
if (min <= 0m)
min = step;
if (volume < min)
volume = min;
if (volume > max)
volume = max;
volume = Math.Floor(volume / step) * step;
if (volume <= 0m)
volume = min;
return volume;
}
private void EnterLong(ICandleMessage candle, decimal volume, decimal stopLossDiff, decimal takeProfitDiff)
{
var orderVolume = volume;
if (Position < 0m)
orderVolume += Math.Abs(Position);
BuyMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice - stopLossDiff;
_takeProfitPrice = _entryPrice + takeProfitDiff;
}
private void EnterShort(ICandleMessage candle, decimal volume, decimal stopLossDiff, decimal takeProfitDiff)
{
var orderVolume = volume;
if (Position > 0m)
orderVolume += Position;
SellMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice + stopLossDiff;
_takeProfitPrice = _entryPrice - takeProfitDiff;
}
private void ManageOpenPosition(ICandleMessage candle, decimal triggerDiff, decimal paddingDiff)
{
if (Position > 0m)
{
if (UseBreakEven && _entryPrice is decimal entry && _stopPrice is decimal currentStop)
{
var triggerPrice = entry + triggerDiff;
var targetStop = entry + paddingDiff;
if (triggerDiff > 0m && candle.HighPrice >= triggerPrice && currentStop < targetStop)
_stopPrice = targetStop;
}
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket();
ResetPositionState();
return;
}
if (_takeProfitPrice is decimal target && candle.HighPrice >= target)
{
SellMarket();
ResetPositionState();
return;
}
}
else if (Position < 0m)
{
if (UseBreakEven && _entryPrice is decimal entry && _stopPrice is decimal currentStop)
{
var triggerPrice = entry - triggerDiff;
var targetStop = entry - paddingDiff;
if (triggerDiff > 0m && candle.LowPrice <= triggerPrice && currentStop > targetStop)
_stopPrice = targetStop;
}
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket();
ResetPositionState();
return;
}
if (_takeProfitPrice is decimal target && candle.LowPrice <= target)
{
BuyMarket();
ResetPositionState();
return;
}
}
else
{
ResetPositionState();
}
}
private void ResetPositionState()
{
_entryPrice = null;
_stopPrice = null;
_takeProfitPrice = null;
}
}
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 (
RelativeStrengthIndex,
MoneyFlowIndex,
AverageTrueRange,
)
class self_optimizing_rsi_or_mfi_trader_v3_strategy(Strategy):
"""Self-optimizing RSI/MFI: dynamically finds best overbought/oversold levels over rolling history."""
def __init__(self):
super(self_optimizing_rsi_or_mfi_trader_v3_strategy, self).__init__()
self._optimizing_periods = self.Param("OptimizingPeriods", 30) \
.SetGreaterThanZero() \
.SetDisplay("Optimization Bars", "Number of bars used for optimization", "General")
self._use_aggressive_entries = self.Param("UseAggressiveEntries", False) \
.SetDisplay("Aggressive Entries", "Allow entries without indicator crosses", "Trading")
self._trade_reverse = self.Param("TradeReverse", False) \
.SetDisplay("Reverse Trading", "Swap profitability preference for opposite trades", "Trading")
self._one_order_at_a_time = self.Param("OneOrderAtATime", True) \
.SetDisplay("One Position", "Permit only one open position", "Trading")
self._base_volume = self.Param("BaseVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Base Volume", "Static order volume when sizing manually", "Risk")
self._use_dynamic_volume = self.Param("UseDynamicVolume", True) \
.SetDisplay("Dynamic Volume", "Use risk percentage for position sizing", "Risk")
self._risk_percent = self.Param("RiskPercent", 2.0) \
.SetDisplay("Risk %", "Percent of capital risked per trade", "Risk")
# 0=RSI, 1=MFI
self._indicator_choice = self.Param("IndicatorChoice", 0) \
.SetDisplay("Indicator", "0=RSI, 1=MFI", "Indicator")
self._indicator_top_value = self.Param("IndicatorTopValue", 100) \
.SetDisplay("Top Level", "Upper bound for level search", "Indicator")
self._indicator_bottom_value = self.Param("IndicatorBottomValue", 0) \
.SetDisplay("Bottom Level", "Lower bound for level search", "Indicator")
self._indicator_period = self.Param("IndicatorPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("Indicator Period", "Averaging period for RSI or MFI", "Indicator")
self._use_dynamic_targets = self.Param("UseDynamicTargets", True) \
.SetDisplay("Dynamic Targets", "Use ATR based stop-loss and take-profit", "Risk")
self._static_stop_loss_points = self.Param("StaticStopLossPoints", 1000) \
.SetGreaterThanZero() \
.SetDisplay("Static Stop", "Stop-loss in points when dynamic targets disabled", "Risk")
self._static_take_profit_points = self.Param("StaticTakeProfitPoints", 2000) \
.SetGreaterThanZero() \
.SetDisplay("Static Take", "Take-profit in points when dynamic targets disabled", "Risk")
self._stop_loss_atr_multiplier = self.Param("StopLossAtrMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("ATR Stop Mult", "Stop-loss multiplier applied to ATR", "Risk")
self._take_profit_atr_multiplier = self.Param("TakeProfitAtrMultiplier", 7.0) \
.SetGreaterThanZero() \
.SetDisplay("ATR Take Mult", "Take-profit multiplier applied to ATR", "Risk")
self._use_break_even = self.Param("UseBreakEven", True) \
.SetDisplay("Use Breakeven", "Move stop to breakeven after trigger", "Risk")
self._break_even_trigger_points = self.Param("BreakEvenTriggerPoints", 200) \
.SetGreaterThanZero() \
.SetDisplay("Breakeven Trigger", "Profit in points required to arm breakeven", "Risk")
self._break_even_padding_points = self.Param("BreakEvenPaddingPoints", 100) \
.SetGreaterThanZero() \
.SetDisplay("Breakeven Padding", "Padding in points applied after trigger", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe used for analysis", "General")
self._history = []
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
@property
def OptimizingPeriods(self):
return int(self._optimizing_periods.Value)
@property
def UseAggressiveEntries(self):
return self._use_aggressive_entries.Value
@property
def TradeReverse(self):
return self._trade_reverse.Value
@property
def OneOrderAtATime(self):
return self._one_order_at_a_time.Value
@property
def BaseVolume(self):
return float(self._base_volume.Value)
@property
def UseDynamicVolume(self):
return self._use_dynamic_volume.Value
@property
def RiskPercent(self):
return float(self._risk_percent.Value)
@property
def IndicatorChoice(self):
return int(self._indicator_choice.Value)
@property
def IndicatorTopValue(self):
return int(self._indicator_top_value.Value)
@property
def IndicatorBottomValue(self):
return int(self._indicator_bottom_value.Value)
@property
def IndicatorPeriod(self):
return int(self._indicator_period.Value)
@property
def UseDynamicTargets(self):
return self._use_dynamic_targets.Value
@property
def StaticStopLossPoints(self):
return int(self._static_stop_loss_points.Value)
@property
def StaticTakeProfitPoints(self):
return int(self._static_take_profit_points.Value)
@property
def StopLossAtrMultiplier(self):
return float(self._stop_loss_atr_multiplier.Value)
@property
def TakeProfitAtrMultiplier(self):
return float(self._take_profit_atr_multiplier.Value)
@property
def UseBreakEven(self):
return self._use_break_even.Value
@property
def BreakEvenTriggerPoints(self):
return int(self._break_even_trigger_points.Value)
@property
def BreakEvenPaddingPoints(self):
return int(self._break_even_padding_points.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(self_optimizing_rsi_or_mfi_trader_v3_strategy, self).OnStarted2(time)
self._history = []
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
if self.IndicatorChoice == 1:
self._indicator = MoneyFlowIndex()
else:
self._indicator = RelativeStrengthIndex()
self._indicator.Length = self.IndicatorPeriod
self._atr = AverageTrueRange()
self._atr.Length = self.IndicatorPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._indicator, self._atr, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._indicator)
self.DrawIndicator(area, self._atr)
self.DrawOwnTrades(area)
def process_candle(self, candle, indicator_value, atr_value):
if candle.State != CandleStates.Finished:
return
ind_val = float(indicator_value)
atr_val = float(atr_value)
close = float(candle.ClosePrice)
self._history.append((ind_val, close))
max_needed = max(self.OptimizingPeriods + 1, 3)
while len(self._history) > max_needed:
self._history.pop(0)
sec = self.Security
price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
if price_step <= 0:
price_step = 1.0
trigger_diff = self.BreakEvenTriggerPoints * price_step if self.UseBreakEven else 0.0
padding_pts = self.BreakEvenPaddingPoints if self.BreakEvenPaddingPoints <= self.BreakEvenTriggerPoints else 0
padding_diff = padding_pts * price_step if self.UseBreakEven else 0.0
self._manage_open_position(candle, trigger_diff, padding_diff)
if len(self._history) < max_needed:
return
# Build arrays (newest first)
indicator_values = []
close_values = []
for i in range(len(self._history) - 1, -1, -1):
indicator_values.append(self._history[i][0])
close_values.append(self._history[i][1])
if self.UseDynamicTargets:
if atr_val <= 0:
return
stop_loss_diff = atr_val * self.StopLossAtrMultiplier
take_profit_diff = atr_val * self.TakeProfitAtrMultiplier
else:
stop_loss_diff = self.StaticStopLossPoints * price_step
take_profit_diff = self.StaticTakeProfitPoints * price_step
if stop_loss_diff <= 0 or take_profit_diff <= 0:
return
volume = self.BaseVolume
step_multiplier = 1.0
sell_level, sell_profit = self._calc_best_sell_level(indicator_values, close_values, stop_loss_diff, take_profit_diff, volume, step_multiplier)
buy_level, buy_profit = self._calc_best_buy_level(indicator_values, close_values, stop_loss_diff, take_profit_diff, volume, step_multiplier)
adjusted_sell = sell_profit
adjusted_buy = buy_profit
if self.TradeReverse:
adjusted_sell = buy_profit
adjusted_buy = sell_profit
can_enter = not self.OneOrderAtATime or self.Position == 0
current_ind = indicator_values[0]
prev_ind = indicator_values[1]
if adjusted_sell > adjusted_buy:
if can_enter and ((current_ind < sell_level and prev_ind > sell_level) or self.UseAggressiveEntries):
self._enter_short(candle, stop_loss_diff, take_profit_diff)
elif adjusted_sell < adjusted_buy:
if can_enter and ((current_ind > buy_level and prev_ind < buy_level) or self.UseAggressiveEntries):
self._enter_long(candle, stop_loss_diff, take_profit_diff)
def _calc_best_sell_level(self, ind_vals, close_vals, sl_diff, tp_diff, volume, step_mult):
bottom = min(self.IndicatorBottomValue, self.IndicatorTopValue)
top = max(self.IndicatorBottomValue, self.IndicatorTopValue)
best_profit = 0.0
best_level = bottom
updated = False
for level in range(bottom, top + 1):
profit = self._eval_sell(ind_vals, close_vals, level, sl_diff, tp_diff, volume, step_mult)
if profit > best_profit:
best_profit = profit
best_level = level
updated = True
return (best_level, best_profit if updated else 0.0)
def _calc_best_buy_level(self, ind_vals, close_vals, sl_diff, tp_diff, volume, step_mult):
bottom = min(self.IndicatorBottomValue, self.IndicatorTopValue)
top = max(self.IndicatorBottomValue, self.IndicatorTopValue)
best_profit = 0.0
best_level = top
updated = False
for level in range(top, bottom - 1, -1):
profit = self._eval_buy(ind_vals, close_vals, level, sl_diff, tp_diff, volume, step_mult)
if profit > best_profit:
best_profit = profit
best_level = level
updated = True
return (best_level, best_profit if updated else 0.0)
def _eval_sell(self, ind_vals, close_vals, level, sl_diff, tp_diff, volume, step_mult):
total = 0.0
n = len(ind_vals)
if n < 3:
return 0.0
threshold = float(level)
i = n - 2
while i >= 2:
if ind_vals[i] < threshold and ind_vals[i + 1] > threshold:
entry = close_vals[i]
j = i - 1
while j >= 1:
price = close_vals[j]
if price >= entry + sl_diff:
total -= (price - entry) * step_mult * volume
i = j
break
if price <= entry - tp_diff:
total += (entry - price) * step_mult * volume
i = j
break
j -= 1
i -= 1
return total
def _eval_buy(self, ind_vals, close_vals, level, sl_diff, tp_diff, volume, step_mult):
total = 0.0
n = len(ind_vals)
if n < 3:
return 0.0
threshold = float(level)
i = n - 2
while i >= 2:
if ind_vals[i] > threshold and ind_vals[i + 1] < threshold:
entry = close_vals[i]
j = i - 1
while j >= 1:
price = close_vals[j]
if price <= entry - sl_diff:
total -= (entry - price) * step_mult * volume
i = j
break
if price >= entry + tp_diff:
total += (price - entry) * step_mult * volume
i = j
break
j -= 1
i -= 1
return total
def _enter_long(self, candle, sl_diff, tp_diff):
self.BuyMarket()
self._entry_price = float(candle.ClosePrice)
self._stop_price = self._entry_price - sl_diff
self._take_profit_price = self._entry_price + tp_diff
def _enter_short(self, candle, sl_diff, tp_diff):
self.SellMarket()
self._entry_price = float(candle.ClosePrice)
self._stop_price = self._entry_price + sl_diff
self._take_profit_price = self._entry_price - tp_diff
def _manage_open_position(self, candle, trigger_diff, padding_diff):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self.Position > 0:
if self.UseBreakEven and self._entry_price is not None and self._stop_price is not None:
trigger_price = self._entry_price + trigger_diff
target_stop = self._entry_price + padding_diff
if trigger_diff > 0 and h >= trigger_price and self._stop_price < target_stop:
self._stop_price = target_stop
if self._stop_price is not None and lo <= self._stop_price:
self.SellMarket()
self._reset_position_state()
return
if self._take_profit_price is not None and h >= self._take_profit_price:
self.SellMarket()
self._reset_position_state()
return
elif self.Position < 0:
if self.UseBreakEven and self._entry_price is not None and self._stop_price is not None:
trigger_price = self._entry_price - trigger_diff
target_stop = self._entry_price - padding_diff
if trigger_diff > 0 and lo <= trigger_price and self._stop_price > target_stop:
self._stop_price = target_stop
if self._stop_price is not None and h >= self._stop_price:
self.BuyMarket()
self._reset_position_state()
return
if self._take_profit_price is not None and lo <= self._take_profit_price:
self.BuyMarket()
self._reset_position_state()
return
else:
self._reset_position_state()
def _reset_position_state(self):
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
def OnReseted(self):
super(self_optimizing_rsi_or_mfi_trader_v3_strategy, self).OnReseted()
self._history = []
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
def CreateClone(self):
return self_optimizing_rsi_or_mfi_trader_v3_strategy()