Operador RSI o MFI Autooptimizante v3
Descripción general
Esta estrategia porta el asesor experto de MetaTrader "Self Optimizing RSI or MFI Trader" a la API de alto nivel de StockSharp. En cada vela finalizada el algoritmo realiza un backtesting de una ventana deslizante de barras históricas y encuentra los umbrales de sobrecompra y sobreventa más rentables para el oscilador seleccionado. Las operaciones en vivo solo se realizan cuando el valor actual del oscilador cruza el umbral con mejor rendimiento en la misma dirección que la ventaja histórica, opcionalmente sin requerir un cruce en el modo "agresivo". Las salidas de posición dependen de stops y objetivos basados en ATR o de distancia fija con un paso de punto de equilibrio opcional.
Datos de mercado
- Funciona con cualquier instrumento que proporcione velas OHLC y volumen (MFI requiere volumen).
- Usa el marco temporal especificado por el parámetro
CandleType. El predeterminado son velas de 15 minutos, pero puede adjuntar cualquier marco temporal compatible con el adaptador de la plataforma.
Indicadores
- Relative Strength Index (RSI) o Money Flow Index (MFI) dependiendo del parámetro
IndicatorChoice. Ambos comparten la misma longitud de promediado. - Average True Range (ATR) para el dimensionamiento de stop-loss/take-profit basado en ATR cuando
UseDynamicTargetsestá habilitado.
Lógica de trading
- Mantener un historial continuo de
OptimizingPeriods+ 1 velas finalizadas con sus valores de oscilador y precios de cierre. - Para cada nivel entero entre
IndicatorBottomValueeIndicatorTopValuela estrategia simula operaciones en la ventana histórica:- Simulación corta: contar cuántas veces el oscilador cruzó por debajo del nivel y si un stop-loss o take-profit corto habría sido alcanzado primero.
- Simulación larga: contar cuántas veces el oscilador cruzó por encima del nivel y cuán rentables habrían sido las operaciones.
- Elegir el umbral que entregó la mayor ganancia simulada para cada dirección. Si
TradeReverseestá habilitado, las puntuaciones de rentabilidad se intercambian para que la dirección opuesta se favorezca. - Cuando el oscilador en vivo cruza el mejor nivel en la dirección rentable (o inmediatamente cuando
UseAggressiveEntrieses verdadero) la estrategia abre una posición, respetandoOneOrderAtATime. - Gestión de salida:
- Los niveles de stop-loss y take-profit se calculan ya sea a partir de múltiplos de ATR (
StopLossAtrMultiplier,TakeProfitAtrMultiplier) o de distancias fijas en puntos (StaticStopLossPoints,StaticTakeProfitPoints). UseBreakEvenmueve el stop al precio de entrada másBreakEvenPaddingPointsuna vez que la ganancia no realizada alcanzaBreakEvenTriggerPoints.- Las posiciones se cierran cuando se cruzan los precios de stop-loss o take-profit.
- Los niveles de stop-loss y take-profit se calculan ya sea a partir de múltiplos de ATR (
Gestión de riesgo
- Dimensionamiento dinámico: cuando
UseDynamicVolumees verdadero la estrategia arriesgaRiskPercentdel valor actual del portafolio. El cálculo convierte la distancia del stop en riesgo monetario usando elPriceStepyStepPricedel instrumento. - Dimensionamiento estático: cuando está deshabilitado, se operan
BaseVolumelotes en cada entrada. - Protección de punto de equilibrio: asegura que las operaciones ganadoras estén protegidas una vez que se haya acumulado suficiente ganancia.
Parámetros
| Parámetro | Descripción |
|---|---|
OptimizingPeriods |
Número de barras usadas para la optimización continua en muestra (predeterminado 144). |
IndicatorChoice |
Elige RSI o MFI como el oscilador principal. |
IndicatorPeriod |
Período de promediado para el oscilador y ATR. |
IndicatorTopValue / IndicatorBottomValue |
Límites de búsqueda para niveles de umbral (típicamente 0–100). |
UseAggressiveEntries |
Si es verdadero, permite entradas sin un cruce confirmado. |
TradeReverse |
Intercambia puntuaciones de rentabilidad para operar el lado históricamente perdedor. |
OneOrderAtATime |
Previene abrir una nueva posición mientras otra está activa. |
UseDynamicTargets |
Alterna entre stops/objetivos basados en ATR y de punto fijo. |
StopLossAtrMultiplier, TakeProfitAtrMultiplier |
Multiplicadores de ATR para salidas dinámicas. |
StaticStopLossPoints, StaticTakeProfitPoints |
Distancias en puntos para salidas fijas. |
UseBreakEven, BreakEvenTriggerPoints, BreakEvenPaddingPoints |
Configurar el comportamiento del stop de punto de equilibrio. |
UseDynamicVolume, RiskPercent, BaseVolume |
Controlar la lógica de dimensionamiento de posición. |
CandleType |
Marco temporal para optimización y trading. |
Notas de implementación
- La estrategia usa el pipeline
SubscribeCandles().Bind(...)de StockSharp, por lo que solo se ejecuta en velas completadas. OneOrderAtATimedebe permanecer habilitado cuando se opera en una cuenta de netting, porque la implementación rastrea una sola posición agregada.- Las salidas basadas en ATR requieren un valor ATR válido; la estrategia omitirá el trading hasta que el indicador esté completamente formado.
- Cuando se usa MFI, asegúrese de que el feed de datos suministre volumen, de lo contrario el indicador devuelve cero y no se generarán operaciones.
Consejos de optimización
- Optimice
OptimizingPeriods, el período del oscilador y los multiplicadores de ATR juntos para que coincidan con el régimen de volatilidad del instrumento. - Diferentes activos pueden beneficiarse de rangos de nivel más estrechos (p. ej., 20–80) para reducir el ruido.
- Considere realizar pruebas prospectivas con análisis walk-forward porque la estrategia adapta los umbrales continuamente.
Uso
- Agregue la estrategia a un conector en el Designer o ejecútela programáticamente.
- Establezca el instrumento deseado, la cartera y los valores de los parámetros.
- Inicie la estrategia; comenzará a operar una vez que se hayan acumulado suficientes velas para la optimización.
Limitaciones
- La optimización histórica ocurre en cada barra y puede ser intensiva en CPU para
OptimizingPeriodsmuy grandes o rangos de niveles amplios. - Debido a que los niveles son enteros, no se prueban umbrales de grano fino (p. ej., 70.5).
- El enfoque asume que el pasado reciente sigue siendo predictivo; los cambios repentinos de régimen pueden degradar el rendimiento, así que monitoree los resultados en vivo y ajuste la configuración cuando sea necesario.
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()