Estrategia Hans Indicator Sistema de Nube
Descripción general
Esta estrategia porta el asesor experto MQL5 Exp_Hans_Indicator_Cloud_System a la API de alto nivel de StockSharp. Reproduce los
rangos de "nube" del indicador Hans que dividen cada día de trading en dos sesiones de referencia y opera cuando el indicador reporta un
breakout por encima o por debajo de esos rangos dinámicos. La implementación consume una serie de velas configurable (predeterminado: M30), procesa
solo velas finalizadas, y refleja la lógica de ejecución retardada del script original actuando en la siguiente barra después de un cambio
de color.
Recreación del indicador Hans
El indicador original desplaza todas las marcas de tiempo desde la zona horaria del broker (LocalTimeZone) a una zona horaria objetivo (DestinationTimeZone).
El port de StockSharp aplica el mismo desplazamiento antes de dividir cada día en dos sesiones:
- Sesión 1 (04:00–08:00 hora objetivo) – la estrategia registra el máximo más alto y el mínimo más bajo de todas las velas que caen dentro
de esta ventana. Una vez que la ventana termina, la zona se considera completa.
- Sesión 2 (08:00–12:00 hora objetivo) – el proceso se repite para la segunda ventana. Cuando esta sesión termina, sus valores alto/bajo
supersedan la primera zona por el resto del día.
Un buffer configurable (PipsForEntry) expresado en pasos de precio se agrega por encima del máximo y por debajo del mínimo de la zona activa. El
mapa de colores del indicador se reproduce de la siguiente manera:
0 – el cierre está por encima de la zona superior y el cuerpo de la vela es alcista.
1 – el cierre está por encima de la zona superior y el cuerpo de la vela es bajista.
3 – el cierre está por debajo de la zona inferior y el cuerpo de la vela es alcista.
4 – el cierre está por debajo de la zona inferior y el cuerpo de la vela es bajista.
2 – sin breakout (estado neutral).
Estos valores se almacenan para emular las búsquedas de CopyBuffer realizadas por el experto MQL5.
Lógica de trading
- La estrategia mantiene un historial rodante de códigos de color y mira atrás
SignalBar barras (predeterminado 1) más una barra extra, coincidiendo con
la llamada CopyBuffer(..., SignalBar, 2, ...) del fuente.
- Abrir largo: la barra más antigua (
SignalBar + 1) reporta color 0 o 1 y la barra más reciente (SignalBar) no está coloreada
0/1. Cualquier exposición corta existente se cierra antes de abrir un nuevo largo de TradeVolume unidades.
- Abrir corto: la barra más antigua reporta color
3 o 4 y la barra más reciente no está coloreada 3/4. Cualquier exposición larga
existente se aplana primero y luego se abre un nuevo corto.
- Cerrar largo: cuando la barra más antigua está coloreada
3 o 4 y los cierres largos están habilitados.
- Cerrar corto: cuando la barra más antigua está coloreada
0 o 1 y los cierres cortos están habilitados.
Las salidas se procesan antes que las entradas exactamente como las funciones auxiliares dentro de TradeAlgorithms.mqh, asegurando que las posiciones opuestas
se cierren antes de emitir nuevas órdenes.
Parámetros
- Tipo de vela (
CandleType): marco temporal de las velas procesadas.
- Barra de señal (
SignalBar): cuántas velas finalizadas atrás inspeccionar para un cambio de color.
- Zona horaria local (
LocalTimeZone): zona horaria del broker/servidor en horas.
- Zona horaria de destino (
DestinationTimeZone): zona horaria objetivo que define las ventanas de sesión.
- Buffer de breakout (
PipsForEntry): número de pasos de precio agregados por encima/por debajo del rango de sesión detectado.
- Habilitar entradas/salidas largas (
BuyPosOpen, BuyPosClose): interruptores para gestionar posiciones largas.
- Habilitar entradas/salidas cortas (
SellPosOpen, SellPosClose): interruptores para gestionar posiciones cortas.
- Volumen de trading (
TradeVolume): tamaño de orden usado para cada nueva posición; también sincronizado con Strategy.Volume al inicio.
Notas
- La traducción de Python se omite intencionalmente según lo solicitado.
- Los auxiliares de gestión de dinero de
TradeAlgorithms.mqh (modos de margen, dimensionamiento de posición dinámico, colocación de stop-loss/take-profit)
se simplifican a un volumen de trading fijo y reglas de salida explícitas.
- Cuando el valor no expone
PriceStep, el buffer de breakout se interpreta como unidades de precio absolutas, coincidiendo con la mejor
aproximación disponible sin información sobre el tamaño del tick.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
public class HansIndicatorCloudSystemStrategy : Strategy
{
private static readonly TimeSpan Period1Start = TimeSpan.FromHours(4);
private static readonly TimeSpan Period1End = TimeSpan.FromHours(8);
private static readonly TimeSpan Period2End = TimeSpan.FromHours(12);
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<int> _localTimeZone;
private readonly StrategyParam<int> _destinationTimeZone;
private readonly StrategyParam<decimal> _pipsForEntry;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose;
private readonly StrategyParam<bool> _sellPosClose;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly List<int> _colorHistory = new();
private DayState _currentDay;
private TimeSpan _timeShift;
private int _cooldownLeft;
public HansIndicatorCloudSystemStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle type", "Primary timeframe analysed by the strategy.", "General");
_signalBar = Param(nameof(SignalBar), 1)
.SetNotNegative()
.SetDisplay("Signal bar", "Historical bar index inspected for colour changes.", "Signals");
_localTimeZone = Param(nameof(LocalTimeZone), 0)
.SetDisplay("Local timezone", "Broker/server timezone used by the raw candles (hours).", "Time zones");
_destinationTimeZone = Param(nameof(DestinationTimeZone), 4)
.SetDisplay("Destination timezone", "Target timezone for Hans ranges (hours).", "Time zones");
_pipsForEntry = Param(nameof(PipsForEntry), 300m)
.SetNotNegative()
.SetDisplay("Breakout buffer", "Extra price steps added above/below the session ranges.", "Indicator");
_cooldownBars = Param(nameof(CooldownBars), 48)
.SetNotNegative()
.SetDisplay("Cooldown bars", "Bars to wait after a close or entry before another entry.", "Trading");
_buyPosOpen = Param(nameof(BuyPosOpen), true)
.SetDisplay("Enable long entries", "Allow opening new long positions when an upper breakout appears.", "Trading");
_sellPosOpen = Param(nameof(SellPosOpen), true)
.SetDisplay("Enable short entries", "Allow opening new short positions when a lower breakout appears.", "Trading");
_buyPosClose = Param(nameof(BuyPosClose), true)
.SetDisplay("Enable long exits", "Allow closing existing longs on a bearish breakout.", "Trading");
_sellPosClose = Param(nameof(SellPosClose), true)
.SetDisplay("Enable short exits", "Allow closing existing shorts on a bullish breakout.", "Trading");
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade volume", "Order size used for every new position.", "Trading");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
public int LocalTimeZone
{
get => _localTimeZone.Value;
set => _localTimeZone.Value = value;
}
public int DestinationTimeZone
{
get => _destinationTimeZone.Value;
set => _destinationTimeZone.Value = value;
}
public decimal PipsForEntry
{
get => _pipsForEntry.Value;
set => _pipsForEntry.Value = value;
}
public bool BuyPosOpen
{
get => _buyPosOpen.Value;
set => _buyPosOpen.Value = value;
}
public bool SellPosOpen
{
get => _sellPosOpen.Value;
set => _sellPosOpen.Value = value;
}
public bool BuyPosClose
{
get => _buyPosClose.Value;
set => _buyPosClose.Value = value;
}
public bool SellPosClose
{
get => _sellPosClose.Value;
set => _sellPosClose.Value = value;
}
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_timeShift = default;
_currentDay = null;
_colorHistory.Clear();
_cooldownLeft = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = TradeVolume; // Keep the default Strategy volume aligned with the configured trade size.
_timeShift = TimeSpan.FromHours(DestinationTimeZone - LocalTimeZone);
_currentDay = null;
_colorHistory.Clear();
_cooldownLeft = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var color = CalculateColor(candle);
_colorHistory.Add(color); // Store Hans indicator colour codes for historical lookups.
if (_cooldownLeft > 0)
_cooldownLeft--;
var maxHistory = Math.Max(5, SignalBar + 3);
if (_colorHistory.Count > maxHistory)
_colorHistory.RemoveAt(0); // Keep just enough history for signal evaluation.
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Align the history pointer with the requested SignalBar offset.
var targetIndex = _colorHistory.Count - 1 - SignalBar;
if (targetIndex <= 0)
return;
// Evaluate the Hans indicator codes for breakout conditions.
var col0 = _colorHistory[targetIndex];
var col1 = _colorHistory[targetIndex - 1];
var bullishBreakout = col1 == 0 || col1 == 1;
var bearishBreakout = col1 == 3 || col1 == 4;
// Prepare trading decisions that mimic TradeAlgorithms.mqh helper flags.
var shouldCloseShort = SellPosClose && bullishBreakout;
var shouldOpenLong = BuyPosOpen && bullishBreakout && col0 != 0 && col0 != 1;
var shouldCloseLong = BuyPosClose && bearishBreakout;
var shouldOpenShort = SellPosOpen && bearishBreakout && col0 != 3 && col0 != 4;
// Close existing long positions before handling new entries.
if (shouldCloseLong && Position > 0)
{
var volume = Position;
if (volume > 0)
SellMarket(volume);
_cooldownLeft = CooldownBars;
return;
}
// Close existing short positions before handling new entries.
if (shouldCloseShort && Position < 0)
{
var volume = Math.Abs(Position);
if (volume > 0)
BuyMarket(volume);
_cooldownLeft = CooldownBars;
return;
}
// Flatten any opposite exposure before opening a fresh long trade.
if (_cooldownLeft == 0 && shouldOpenLong && Position <= 0 && TradeVolume > 0)
{
if (Position < 0)
{
var covering = Math.Abs(Position);
if (covering > 0)
BuyMarket(covering);
}
BuyMarket(TradeVolume);
_cooldownLeft = CooldownBars;
}
// Flatten any opposite exposure before opening a fresh short trade.
else if (_cooldownLeft == 0 && shouldOpenShort && Position >= 0 && TradeVolume > 0)
{
if (Position > 0)
{
var covering = Position;
if (covering > 0)
SellMarket(covering);
}
SellMarket(TradeVolume);
_cooldownLeft = CooldownBars;
}
}
private int CalculateColor(ICandleMessage candle)
{
var shiftedTime = candle.OpenTime + _timeShift;
var day = shiftedTime.Date;
// Build or reset the daily session state after applying the timezone shift.
if (_currentDay == null || _currentDay.Date != day)
_currentDay = new DayState(day);
UpdateSessionExtremes(_currentDay, candle, shiftedTime.TimeOfDay);
var zone = GetActiveZone(_currentDay);
if (zone == null)
return 2;
var (upper, lower) = zone.Value;
var close = candle.ClosePrice;
var open = candle.OpenPrice;
// The Hans indicator paints breakout candles with colour codes 0/1 (bullish) and 3/4 (bearish).
if (close > upper)
return close >= open ? 0 : 1;
if (close < lower)
return close <= open ? 4 : 3;
return 2;
}
// Track the two Hans sessions (04:00-08:00 and 08:00-12:00 target time) and their high/low ranges.
private void UpdateSessionExtremes(DayState dayState, ICandleMessage candle, TimeSpan localTime)
{
if (localTime >= Period1Start && localTime < Period1End)
{
// First session: update running high/low.
dayState.Period1Seen = true;
dayState.Period1High = dayState.Period1High.HasValue
? Math.Max(dayState.Period1High.Value, candle.HighPrice)
: candle.HighPrice;
dayState.Period1Low = dayState.Period1Low.HasValue
? Math.Min(dayState.Period1Low.Value, candle.LowPrice)
: candle.LowPrice;
}
else if (localTime >= Period1End && localTime < Period2End)
{
// Second session: finalise the first zone and accumulate the second zone.
if (!dayState.Period1Closed && dayState.Period1Seen)
dayState.Period1Closed = true;
dayState.Period2Seen = true;
dayState.Period2High = dayState.Period2High.HasValue
? Math.Max(dayState.Period2High.Value, candle.HighPrice)
: candle.HighPrice;
dayState.Period2Low = dayState.Period2Low.HasValue
? Math.Min(dayState.Period2Low.Value, candle.LowPrice)
: candle.LowPrice;
}
else
{
// After the monitored windows we just lock the zones if they received data.
if (!dayState.Period1Closed && dayState.Period1Seen && localTime >= Period1End)
dayState.Period1Closed = true;
if (!dayState.Period2Closed && dayState.Period2Seen && localTime >= Period2End)
dayState.Period2Closed = true;
}
if (localTime >= Period2End && dayState.Period2Seen)
dayState.Period2Closed = true;
}
// Prefer the second session range when available, otherwise fall back to the first session.
private (decimal upper, decimal lower)? GetActiveZone(DayState dayState)
{
var entryOffset = GetEntryOffset();
if (dayState.Period2Closed && dayState.Period2High.HasValue && dayState.Period2Low.HasValue)
{
return (
dayState.Period2High.Value + entryOffset,
dayState.Period2Low.Value - entryOffset);
}
if (dayState.Period1Closed && dayState.Period1High.HasValue && dayState.Period1Low.HasValue)
{
return (
dayState.Period1High.Value + entryOffset,
dayState.Period1Low.Value - entryOffset);
}
return null;
}
// Convert the buffer measured in points into absolute price units.
private decimal GetEntryOffset()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0)
step = 1m;
return PipsForEntry * step;
}
// Container for daily session statistics.
private sealed class DayState
{
public DayState(DateTime date)
{
Date = date;
}
public DateTime Date { get; }
public decimal? Period1High { get; set; }
public decimal? Period1Low { get; set; }
public bool Period1Seen { get; set; }
public bool Period1Closed { get; set; }
public decimal? Period2High { get; set; }
public decimal? Period2Low { get; set; }
public bool Period2Seen { get; set; }
public bool Period2Closed { get; set; }
}
}
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
class hans_indicator_cloud_system_strategy(Strategy):
_PERIOD1_START = 4
_PERIOD1_END = 8
_PERIOD2_END = 12
def __init__(self):
super(hans_indicator_cloud_system_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle type", "Primary timeframe analysed by the strategy", "General")
self._signal_bar = self.Param("SignalBar", 1) \
.SetDisplay("Signal bar", "Historical bar index inspected for colour changes", "Signals")
self._local_time_zone = self.Param("LocalTimeZone", 0) \
.SetDisplay("Local timezone", "Broker/server timezone (hours)", "Time zones")
self._destination_time_zone = self.Param("DestinationTimeZone", 4) \
.SetDisplay("Destination timezone", "Target timezone for Hans ranges (hours)", "Time zones")
self._pips_for_entry = self.Param("PipsForEntry", 300.0) \
.SetDisplay("Breakout buffer", "Extra price steps added above/below the session ranges", "Indicator")
self._cooldown_bars = self.Param("CooldownBars", 48) \
.SetDisplay("Cooldown bars", "Bars to wait after a close or entry", "Trading")
self._buy_pos_open = self.Param("BuyPosOpen", True) \
.SetDisplay("Enable long entries", "Allow opening new long positions", "Trading")
self._sell_pos_open = self.Param("SellPosOpen", True) \
.SetDisplay("Enable short entries", "Allow opening new short positions", "Trading")
self._buy_pos_close = self.Param("BuyPosClose", True) \
.SetDisplay("Enable long exits", "Allow closing existing longs", "Trading")
self._sell_pos_close = self.Param("SellPosClose", True) \
.SetDisplay("Enable short exits", "Allow closing existing shorts", "Trading")
self._color_history = []
self._current_day_date = None
self._p1_high = None
self._p1_low = None
self._p1_seen = False
self._p1_closed = False
self._p2_high = None
self._p2_low = None
self._p2_seen = False
self._p2_closed = False
self._time_shift_hours = 0
self._cooldown_left = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def SignalBar(self):
return self._signal_bar.Value
@property
def LocalTimeZone(self):
return self._local_time_zone.Value
@property
def DestinationTimeZone(self):
return self._destination_time_zone.Value
@property
def PipsForEntry(self):
return self._pips_for_entry.Value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@property
def BuyPosOpen(self):
return self._buy_pos_open.Value
@property
def SellPosOpen(self):
return self._sell_pos_open.Value
@property
def BuyPosClose(self):
return self._buy_pos_close.Value
@property
def SellPosClose(self):
return self._sell_pos_close.Value
def OnReseted(self):
super(hans_indicator_cloud_system_strategy, self).OnReseted()
self._color_history = []
self._current_day_date = None
self._p1_high = None
self._p1_low = None
self._p1_seen = False
self._p1_closed = False
self._p2_high = None
self._p2_low = None
self._p2_seen = False
self._p2_closed = False
self._cooldown_left = 0
def OnStarted2(self, time):
super(hans_indicator_cloud_system_strategy, self).OnStarted2(time)
self._time_shift_hours = self.DestinationTimeZone - self.LocalTimeZone
self._current_day_date = None
self._color_history = []
self._cooldown_left = 0
self._p1_high = None
self._p1_low = None
self._p1_seen = False
self._p1_closed = False
self._p2_high = None
self._p2_low = None
self._p2_seen = False
self._p2_closed = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
color = self._calculate_color(candle)
self._color_history.append(color)
if self._cooldown_left > 0:
self._cooldown_left -= 1
max_history = max(5, self.SignalBar + 3)
if len(self._color_history) > max_history:
self._color_history.pop(0)
target_index = len(self._color_history) - 1 - self.SignalBar
if target_index <= 0:
return
col0 = self._color_history[target_index]
col1 = self._color_history[target_index - 1]
bullish_breakout = col1 == 0 or col1 == 1
bearish_breakout = col1 == 3 or col1 == 4
should_close_short = self.SellPosClose and bullish_breakout
should_open_long = self.BuyPosOpen and bullish_breakout and col0 != 0 and col0 != 1
should_close_long = self.BuyPosClose and bearish_breakout
should_open_short = self.SellPosOpen and bearish_breakout and col0 != 3 and col0 != 4
if should_close_long and self.Position > 0:
self.SellMarket()
self._cooldown_left = self.CooldownBars
return
if should_close_short and self.Position < 0:
self.BuyMarket()
self._cooldown_left = self.CooldownBars
return
if self._cooldown_left == 0 and should_open_long and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_left = self.CooldownBars
elif self._cooldown_left == 0 and should_open_short and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_left = self.CooldownBars
def _calculate_color(self, candle):
shifted_hour = candle.OpenTime.Hour + self._time_shift_hours
shifted_date = candle.OpenTime.Date
if shifted_hour >= 24:
shifted_hour -= 24
shifted_date = shifted_date.AddDays(1)
elif shifted_hour < 0:
shifted_hour += 24
shifted_date = shifted_date.AddDays(-1)
if self._current_day_date is None or self._current_day_date != shifted_date:
self._current_day_date = shifted_date
self._p1_high = None
self._p1_low = None
self._p1_seen = False
self._p1_closed = False
self._p2_high = None
self._p2_low = None
self._p2_seen = False
self._p2_closed = False
self._update_session_extremes(candle, shifted_hour)
zone = self._get_active_zone()
if zone is None:
return 2
upper, lower = zone
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
if close > upper:
return 0 if close >= open_p else 1
if close < lower:
return 4 if close <= open_p else 3
return 2
def _update_session_extremes(self, candle, local_hour):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if local_hour >= self._PERIOD1_START and local_hour < self._PERIOD1_END:
self._p1_seen = True
self._p1_high = max(self._p1_high, high) if self._p1_high is not None else high
self._p1_low = min(self._p1_low, low) if self._p1_low is not None else low
elif local_hour >= self._PERIOD1_END and local_hour < self._PERIOD2_END:
if not self._p1_closed and self._p1_seen:
self._p1_closed = True
self._p2_seen = True
self._p2_high = max(self._p2_high, high) if self._p2_high is not None else high
self._p2_low = min(self._p2_low, low) if self._p2_low is not None else low
else:
if not self._p1_closed and self._p1_seen and local_hour >= self._PERIOD1_END:
self._p1_closed = True
if not self._p2_closed and self._p2_seen and local_hour >= self._PERIOD2_END:
self._p2_closed = True
if local_hour >= self._PERIOD2_END and self._p2_seen:
self._p2_closed = True
def _get_active_zone(self):
entry_offset = self._get_entry_offset()
if self._p2_closed and self._p2_high is not None and self._p2_low is not None:
return (self._p2_high + entry_offset, self._p2_low - entry_offset)
if self._p1_closed and self._p1_high is not None and self._p1_low is not None:
return (self._p1_high + entry_offset, self._p1_low - entry_offset)
return None
def _get_entry_offset(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
return float(self.PipsForEntry) * step
def CreateClone(self):
return hans_indicator_cloud_system_strategy()