Estrategia Color Fisher M11
Descripción general
Color Fisher M11 es una estrategia de seguimiento de tendencia que replica el asesor experto Exp_ColorFisher_m11 de MetaTrader 5. Utiliza una variante personalizada del Fisher Transform que colorea las velas con cinco estados de color para resaltar el momentum extremo alcista y bajista. Las señales se retrasan un número configurable de velas cerradas para evitar operar sobre datos incompletos, mientras que interruptores opcionales permiten desactivar entradas o salidas de cada lado de forma independiente.
Lógica del indicador
La estrategia construye el indicador Color Fisher en tiempo real:
- Determina el máximo más alto y el mínimo más bajo en la ventana Range Periods.
- Normaliza el precio medio de la vela actual dentro de ese rango y aplica Price Smoothing (estilo EMA) para estabilizar las oscilaciones.
- Aplica el Fisher Transform con un factor adicional Index Smoothing para crear el valor final del oscilador.
- Clasifica el oscilador en cinco bandas de color discretas usando los umbrales High Level y Low Level:
0 – fuerte impulso alcista por encima del nivel alto.
1 – momentum alcista moderado entre cero y el nivel alto.
2 – zona neutral alrededor de cero.
3 – momentum bajista moderado entre cero y el nivel bajo.
4 – fuerte impulso bajista por debajo del nivel bajo.
La señal se evalúa Signal Bar velas atrás, imitando el comportamiento del Asesor Experto original. El estado de color anterior también se rastrea para detectar transiciones nuevas hacia las bandas extremas.
Reglas de trading
- Entrada larga – permitida cuando
Enable Buy Entry es verdadero, el color retrasado es igual a 0 (fuerte alcista) y el color previo es diferente de 0. Cualquier exposición corta se revierte y la posición se vuelve larga.
- Entrada corta – permitida cuando
Enable Sell Entry es verdadero, el color retrasado es igual a 4 (fuerte bajista) y el color previo es diferente de 4. Cualquier exposición larga se revierte y la posición se vuelve corta.
- Salida larga – activada cuando
Enable Buy Exit es verdadero y el color retrasado pasa a 3 o 4, señalando control bajista.
- Salida corta – activada cuando
Enable Sell Exit es verdadero y el color retrasado pasa a 0 o 1, señalando control alcista.
Para evitar múltiples órdenes por señal, la estrategia recuerda el tiempo de cierre de la siguiente barra para cada dirección y rechaza nuevas entradas hasta que se complete la siguiente vela.
Gestión de riesgos
Stop Loss (pts) y Take Profit (pts) convierten las distancias en pips originales en pasos de precio absolutos usando el precio de paso del instrumento. Cuando se proporciona una distancia positiva, las órdenes protectoras se activan a través de StartProtection. Establezca cualquier valor en cero para desactivar esa protección.
Parámetros
- Range Periods – longitud del lookback para el rango alto/bajo usado por el Fisher Transform (por defecto 10).
- Price Smoothing – factor de suavizado previo a la transformación, 0…0.99 (por defecto 0.3).
- Index Smoothing – factor de suavizado posterior a la transformación, 0…0.99 (por defecto 0.3).
- High Level / Low Level – umbrales que definen los extremos alcistas y bajistas (por defecto +1.01 y –1.01).
- Signal Bar – número de velas cerradas para retrasar la evaluación de señales (por defecto 1).
- Enable Buy Entry / Enable Sell Entry – interruptores para abrir nuevas operaciones largas o cortas.
- Enable Buy Exit / Enable Sell Exit – interruptores para permitir salidas impulsadas por el indicador.
- Stop Loss (pts) / Take Profit (pts) – distancias protectoras expresadas en pasos de precio.
- Candle Type – marco temporal para la suscripción de velas; por defecto velas de 4 horas.
Notas
- La estrategia usa bindings de alto nivel de StockSharp (
SubscribeCandles().BindEx) y no almacena colecciones históricas más allá del historial mínimo de colores requerido para la señal retrasada.
- En esta versión no se proporciona un puerto Python, de acuerdo con la especificación.
- Agrega la estrategia a un área del gráfico para visualizar tanto el precio como el oscilador Color Fisher calculado.
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>
/// Strategy based on the Color Fisher Transform indicator.
/// Replicates the logic of the MQL5 expert Exp_ColorFisher_m11 with configurable entries and exits.
/// </summary>
public class ColorFisherM11Strategy : Strategy
{
private readonly StrategyParam<int> _rangePeriods;
private readonly StrategyParam<decimal> _priceSmoothing;
private readonly StrategyParam<decimal> _indexSmoothing;
private readonly StrategyParam<decimal> _highLevel;
private readonly StrategyParam<decimal> _lowLevel;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<bool> _enableBuyEntry;
private readonly StrategyParam<bool> _enableSellEntry;
private readonly StrategyParam<bool> _enableBuyExit;
private readonly StrategyParam<bool> _enableSellExit;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private ColorFisherM11Indicator _colorFisher;
private readonly List<int> _colorHistory = new();
private DateTimeOffset? _nextLongTime;
private DateTimeOffset? _nextShortTime;
/// <summary>
/// Range length used to determine the Fisher Transform input window.
/// </summary>
public int RangePeriods
{
get => _rangePeriods.Value;
set => _rangePeriods.Value = value;
}
/// <summary>
/// Price smoothing factor (0..1) applied before the Fisher Transform.
/// </summary>
public decimal PriceSmoothing
{
get => _priceSmoothing.Value;
set => _priceSmoothing.Value = value;
}
/// <summary>
/// Fisher index smoothing factor (0..1) applied after the transform.
/// </summary>
public decimal IndexSmoothing
{
get => _indexSmoothing.Value;
set => _indexSmoothing.Value = value;
}
/// <summary>
/// Upper threshold used for bullish color classification.
/// </summary>
public decimal HighLevel
{
get => _highLevel.Value;
set => _highLevel.Value = value;
}
/// <summary>
/// Lower threshold used for bearish color classification.
/// </summary>
public decimal LowLevel
{
get => _lowLevel.Value;
set => _lowLevel.Value = value;
}
/// <summary>
/// Number of closed bars to wait before acting on a signal.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Enable long entries.
/// </summary>
public bool EnableBuyEntry
{
get => _enableBuyEntry.Value;
set => _enableBuyEntry.Value = value;
}
/// <summary>
/// Enable short entries.
/// </summary>
public bool EnableSellEntry
{
get => _enableSellEntry.Value;
set => _enableSellEntry.Value = value;
}
/// <summary>
/// Enable closing of existing long positions based on the indicator.
/// </summary>
public bool EnableBuyExit
{
get => _enableBuyExit.Value;
set => _enableBuyExit.Value = value;
}
/// <summary>
/// Enable closing of existing short positions based on the indicator.
/// </summary>
public bool EnableSellExit
{
get => _enableSellExit.Value;
set => _enableSellExit.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle type and timeframe used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ColorFisherM11Strategy"/> class.
/// </summary>
public ColorFisherM11Strategy()
{
_rangePeriods = Param(nameof(RangePeriods), 3)
.SetGreaterThanZero()
.SetDisplay("Range Periods", "Lookback window for highs and lows", "Indicator");
_priceSmoothing = Param(nameof(PriceSmoothing), 0.3m)
.SetNotNegative()
.SetRange(0.0001m, 0.99m)
.SetDisplay("Price Smoothing", "Smoothing factor applied before Fisher transform", "Indicator");
_indexSmoothing = Param(nameof(IndexSmoothing), 0.3m)
.SetNotNegative()
.SetRange(0.0001m, 0.99m)
.SetDisplay("Index Smoothing", "Smoothing factor applied after Fisher transform", "Indicator");
_highLevel = Param(nameof(HighLevel), 0.05m)
.SetDisplay("High Level", "Upper level for bullish color", "Indicator");
_lowLevel = Param(nameof(LowLevel), -0.05m)
.SetDisplay("Low Level", "Lower level for bearish color", "Indicator");
_signalBar = Param(nameof(SignalBar), 0)
.SetNotNegative()
.SetDisplay("Signal Bar", "Bars to delay signal execution", "Trading");
_enableBuyEntry = Param(nameof(EnableBuyEntry), true)
.SetDisplay("Enable Buy Entry", "Allow opening long positions", "Trading");
_enableSellEntry = Param(nameof(EnableSellEntry), true)
.SetDisplay("Enable Sell Entry", "Allow opening short positions", "Trading");
_enableBuyExit = Param(nameof(EnableBuyExit), true)
.SetDisplay("Enable Buy Exit", "Allow closing long positions", "Trading");
_enableSellExit = Param(nameof(EnableSellExit), true)
.SetDisplay("Enable Sell Exit", "Allow closing short positions", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss (pts)", "Protective stop distance in price steps", "Protection");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetNotNegative()
.SetDisplay("Take Profit (pts)", "Target distance in price steps", "Protection");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for indicator calculation", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_colorFisher?.Reset();
_colorHistory.Clear();
_nextLongTime = null;
_nextShortTime = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_colorFisher = new ColorFisherM11Indicator
{
RangePeriods = RangePeriods,
PriceSmoothing = PriceSmoothing,
IndexSmoothing = IndexSmoothing,
HighLevel = HighLevel,
LowLevel = LowLevel,
MinRange = Security?.PriceStep ?? 0.0001m
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 1m;
Unit stopLossUnit = StopLossPoints > 0 ? new Unit(step * StopLossPoints, UnitTypes.Absolute) : null;
Unit takeProfitUnit = TakeProfitPoints > 0 ? new Unit(step * TakeProfitPoints, UnitTypes.Absolute) : null;
if (stopLossUnit != null || takeProfitUnit != null)
StartProtection(stopLoss: stopLossUnit, takeProfit: takeProfitUnit);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _colorFisher);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_colorFisher.Process(new CandleIndicatorValue(_colorFisher, candle));
UpdateHistory(_colorFisher.LastColor);
if (!_colorFisher.IsFormed)
return;
// indicator already checked via IsFormed above
var signalColor = GetColor(SignalBar);
var previousColor = GetColor(SignalBar + 1);
if (signalColor is null || previousColor is null)
return;
if (EnableSellExit && signalColor < 2 && Position < 0)
{
BuyMarket();
}
if (EnableBuyExit && signalColor > 2 && Position > 0)
{
SellMarket();
}
var allowLong = !_nextLongTime.HasValue || candle.CloseTime >= _nextLongTime.Value;
var allowShort = !_nextShortTime.HasValue || candle.CloseTime >= _nextShortTime.Value;
if (EnableBuyEntry && allowLong && signalColor <= 1 && previousColor > 1 && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket();
_nextLongTime = candle.CloseTime;
}
else if (EnableSellEntry && allowShort && signalColor >= 3 && previousColor < 3 && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket();
_nextShortTime = candle.CloseTime;
}
}
private void UpdateHistory(int color)
{
_colorHistory.Insert(0, color);
var max = Math.Max(SignalBar + 2, 5);
while (_colorHistory.Count > max)
{
try { _colorHistory.RemoveAt(_colorHistory.Count - 1); } catch { break; }
}
}
private int? GetColor(int index)
{
if (index < 0 || index >= _colorHistory.Count)
return null;
return _colorHistory[index];
}
private sealed class ColorFisherM11Indicator : BaseIndicator
{
public int RangePeriods { get; set; } = 10;
public decimal PriceSmoothing { get; set; } = 0.3m;
public decimal IndexSmoothing { get; set; } = 0.3m;
public decimal HighLevel { get; set; } = 1.01m;
public decimal LowLevel { get; set; } = -1.01m;
public decimal MinRange { get; set; } = 0.0001m;
public int LastColor { get; private set; } = 2;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal _prevFish;
private decimal _prevIndex;
private bool _hasPrevIndex;
private int _count;
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
var candle = input.GetValue<ICandleMessage>();
if (candle == null)
return new DecimalIndicatorValue(this, decimal.Zero, input.Time);
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
_count++;
var length = Math.Max(1, RangePeriods);
while (_highs.Count > length)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
var highest = decimal.MinValue;
var lowest = decimal.MaxValue;
for (var i = 0; i < _highs.Count; i++)
{
if (_highs[i] > highest) highest = _highs[i];
if (_lows[i] < lowest) lowest = _lows[i];
}
var range = highest - lowest;
var minRange = MinRange <= 0m ? 0.0001m : MinRange;
if (range < minRange)
range = minRange;
var midPrice = (candle.HighPrice + candle.LowPrice) / 2m;
var priceLocation = range != 0m ? (midPrice - lowest) / range : 0.99m;
priceLocation = 2m * priceLocation - 1m;
var prevFish = _hasPrevIndex ? _prevFish : priceLocation;
var fish = PriceSmoothing * prevFish + (1m - PriceSmoothing) * priceLocation;
var smoothed = Math.Min(Math.Max(fish, -0.99m), 0.99m);
decimal fisherRaw;
var diff = 1m - smoothed;
if (diff == 0m)
{
fisherRaw = 0m;
}
else
{
var ratio = (1m + smoothed) / diff;
fisherRaw = (decimal)Math.Log((double)ratio);
}
var prevIndex = _hasPrevIndex ? _prevIndex : fisherRaw;
var value = IndexSmoothing * prevIndex + (1m - IndexSmoothing) * fisherRaw;
_prevFish = fish;
_prevIndex = value;
_hasPrevIndex = true;
IsFormed = _count >= length;
var color = 2;
if (value > 0m)
color = value > HighLevel ? 0 : 1;
else if (value < 0m)
color = value < LowLevel ? 4 : 3;
LastColor = color;
return new DecimalIndicatorValue(this, value, input.Time) { IsFinal = true };
}
public override void Reset()
{
base.Reset();
_highs.Clear();
_lows.Clear();
_prevFish = 0m;
_prevIndex = 0m;
_hasPrevIndex = false;
_count = 0;
LastColor = 2;
}
}
}
import clr
import math
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class color_fisher_m11_strategy(Strategy):
"""Color Fisher Transform strategy with configurable entries/exits and SL/TP protection."""
def __init__(self):
super(color_fisher_m11_strategy, self).__init__()
self._range_periods = self.Param("RangePeriods", 3) \
.SetGreaterThanZero() \
.SetDisplay("Range Periods", "Lookback window for highs and lows", "Indicator")
self._price_smoothing = self.Param("PriceSmoothing", 0.3) \
.SetDisplay("Price Smoothing", "Smoothing factor before Fisher transform", "Indicator")
self._index_smoothing = self.Param("IndexSmoothing", 0.3) \
.SetDisplay("Index Smoothing", "Smoothing factor after Fisher transform", "Indicator")
self._high_level = self.Param("HighLevel", 0.05) \
.SetDisplay("High Level", "Upper level for bullish color", "Indicator")
self._low_level = self.Param("LowLevel", -0.05) \
.SetDisplay("Low Level", "Lower level for bearish color", "Indicator")
self._signal_bar = self.Param("SignalBar", 0) \
.SetDisplay("Signal Bar", "Bars to delay signal execution", "Trading")
self._enable_buy_entry = self.Param("EnableBuyEntry", True) \
.SetDisplay("Enable Buy Entry", "Allow opening long positions", "Trading")
self._enable_sell_entry = self.Param("EnableSellEntry", True) \
.SetDisplay("Enable Sell Entry", "Allow opening short positions", "Trading")
self._enable_buy_exit = self.Param("EnableBuyExit", True) \
.SetDisplay("Enable Buy Exit", "Allow closing long positions", "Trading")
self._enable_sell_exit = self.Param("EnableSellExit", True) \
.SetDisplay("Enable Sell Exit", "Allow closing short positions", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 1000) \
.SetDisplay("Stop Loss (pts)", "Protective stop distance in price steps", "Protection")
self._take_profit_points = self.Param("TakeProfitPoints", 2000) \
.SetDisplay("Take Profit (pts)", "Target distance in price steps", "Protection")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for indicator calculation", "General")
# Fisher indicator state
self._highs = []
self._lows = []
self._prev_fish = 0.0
self._prev_index = 0.0
self._has_prev = False
self._fisher_count = 0
self._last_color = 2
# Color history (most recent first)
self._color_history = []
@property
def RangePeriods(self):
return int(self._range_periods.Value)
@property
def PriceSmoothing(self):
return float(self._price_smoothing.Value)
@property
def IndexSmoothing(self):
return float(self._index_smoothing.Value)
@property
def HighLevel(self):
return float(self._high_level.Value)
@property
def LowLevel(self):
return float(self._low_level.Value)
@property
def SignalBar(self):
return int(self._signal_bar.Value)
@property
def EnableBuyEntry(self):
return self._enable_buy_entry.Value
@property
def EnableSellEntry(self):
return self._enable_sell_entry.Value
@property
def EnableBuyExit(self):
return self._enable_buy_exit.Value
@property
def EnableSellExit(self):
return self._enable_sell_exit.Value
@property
def StopLossPoints(self):
return int(self._stop_loss_points.Value)
@property
def TakeProfitPoints(self):
return int(self._take_profit_points.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(color_fisher_m11_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._prev_fish = 0.0
self._prev_index = 0.0
self._has_prev = False
self._fisher_count = 0
self._last_color = 2
self._color_history = []
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
sl = self.StopLossPoints * step if self.StopLossPoints > 0 else 0.0
tp = self.TakeProfitPoints * step if self.TakeProfitPoints > 0 else 0.0
if sl > 0 or tp > 0:
self.StartProtection(
stopLoss=Unit(sl, UnitTypes.Absolute) if sl > 0 else None,
takeProfit=Unit(tp, UnitTypes.Absolute) if tp > 0 else None
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _calc_fisher(self, candle):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
self._highs.append(h)
self._lows.append(lo)
self._fisher_count += 1
length = max(1, self.RangePeriods)
while len(self._highs) > length:
self._highs.pop(0)
self._lows.pop(0)
highest = max(self._highs)
lowest = min(self._lows)
sec = self.Security
min_range = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 0.0001
rng = highest - lowest
if rng < min_range:
rng = min_range
mid = (h + lo) / 2.0
price_loc = (mid - lowest) / rng if rng != 0 else 0.99
price_loc = 2.0 * price_loc - 1.0
prev_fish = self._prev_fish if self._has_prev else price_loc
fish = self.PriceSmoothing * prev_fish + (1.0 - self.PriceSmoothing) * price_loc
smoothed = min(max(fish, -0.99), 0.99)
diff = 1.0 - smoothed
if diff == 0:
fisher_raw = 0.0
else:
ratio = (1.0 + smoothed) / diff
fisher_raw = math.log(ratio)
prev_idx = self._prev_index if self._has_prev else fisher_raw
value = self.IndexSmoothing * prev_idx + (1.0 - self.IndexSmoothing) * fisher_raw
self._prev_fish = fish
self._prev_index = value
self._has_prev = True
is_formed = self._fisher_count >= length
color = 2
if value > 0:
color = 0 if value > self.HighLevel else 1
elif value < 0:
color = 4 if value < self.LowLevel else 3
self._last_color = color
return color, is_formed
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
color, is_formed = self._calc_fisher(candle)
self._color_history.insert(0, color)
mx = max(self.SignalBar + 2, 5)
while len(self._color_history) > mx:
self._color_history.pop()
if not is_formed:
return
sig_bar = self.SignalBar
signal_color = self._get_color(sig_bar)
prev_color = self._get_color(sig_bar + 1)
if signal_color is None or prev_color is None:
return
if self.EnableSellExit and signal_color < 2 and self.Position < 0:
self.BuyMarket()
if self.EnableBuyExit and signal_color > 2 and self.Position > 0:
self.SellMarket()
if self.EnableBuyEntry and signal_color <= 1 and prev_color > 1 and self.Position <= 0:
self.BuyMarket()
elif self.EnableSellEntry and signal_color >= 3 and prev_color < 3 and self.Position >= 0:
self.SellMarket()
def _get_color(self, index):
if index < 0 or index >= len(self._color_history):
return None
return self._color_history[index]
def OnReseted(self):
super(color_fisher_m11_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._prev_fish = 0.0
self._prev_index = 0.0
self._has_prev = False
self._fisher_count = 0
self._last_color = 2
self._color_history = []
def CreateClone(self):
return color_fisher_m11_strategy()