Estrategia MacdPatternTraderV01
Descripción general
MacdPatternTraderV01Strategy es un fiel StockSharp puerto del asesor experto FORTRADER "MacdPatternTraderv01" MetaTrader 4. El sistema busca MACD patrones de gancho que aparecen después de que el oscilador se estira hasta un nivel extremo y luego retrocede hacia la línea cero. Cuando se forma un gancho bajista después de un pico de sobrecompra, la estrategia abre posiciones cortas, mientras que un gancho alcista después de una caída de sobreventa activa posiciones largas. La versión StockSharp conserva la gestión de riesgos original de múltiples capas, incluidos niveles recursivos de stop-loss y take-profit, así como escalado de posiciones por etapas.
La implementación de C# utiliza la suscripción de vela de alto nivel API con los indicadores MACD, ExponentialMovingAverage y SimpleMovingAverage. Todos los cálculos se realizan en velas terminadas, reflejando las llamadas iMACD y iMA con cambios de barra explícitos de la versión MQL. La lógica auxiliar adicional rastrea manualmente los máximos y mínimos recientes para reproducir las búsquedas de precios recursivas que EA utiliza para las órdenes de protección.
Lógica de señal
- Condiciones de armado
- Una configuración bajista se activa una vez que la línea principal MACD excede
BearishThreshold. La bandera de armado se borra tan pronto como MACD cruza por debajo de cero.
- Una configuración alcista se activa una vez que la línea principal MACD cae por debajo de
BullishThreshold. La bandera se borra cuando MACD se vuelve positivo.
- Confirmación de gancho
- Las entradas cortas requieren que
macd₀ < BearishThreshold, macd₀ < macd₁, macd₁ > macd₂, la bandera bajista permanezca activa y macd₂ < BearishThreshold mientras macd₀ se mantenga por encima de cero.
- Las entradas largas requieren que
macd₀ > BullishThreshold, macd₀ > macd₁, macd₁ < macd₂, la bandera alcista permanezca activa y macd₂ > BullishThreshold mientras que macd₀ permanezca negativo.
- Ejecución de orden
- Cuando se completa el gancho, la estrategia envía una orden de mercado con el volumen
OrderVolume. Almacena simultáneamente los precios de stop-loss y take-profit calculados para su posterior seguimiento.
Gestión del riesgo
Stop Loss
El stop-loss imita la función MQL StopLoss(type):
- Las operaciones cortas buscan el máximo más alto en las últimas
StopLossBars velas excluyendo la barra recién cerrada, luego agregan OffsetPoints * PriceStep al resultado.
- Las operaciones largas buscan el mínimo más bajo de las últimas
StopLossBars velas históricas, restando la misma compensación.
Esta lógica se implementa con búsquedas extremas manuales en un búfer en memoria limitado (1000 valores) para evitar la creación de grandes colecciones personalizadas.
Tomar ganancias
La toma de ganancias reproduce la rutina recursiva TakeProfit(type) MQL:
- Comience con el bloque más reciente de valores
TakeProfitBars. Incluya la vela que activó la señal.
- Calcule el extremo (bajo para cortos, alto para largos) dentro de ese bloque.
- Retroceda
TakeProfitBars velas y repita mientras el nuevo bloque produce un extremo más favorable.
- Deténgase en el primer bloque que no mejora el extremo y utilice el último valor registrado como obtención de ganancias.
Gestión de posiciones parciales
- Después de la entrada, la estrategia registra el volumen original y el precio de entrada.
- Las salidas parciales se permiten sólo después de que el beneficio flotante expresado en la moneda de la cuenta supere
ProfitThreshold.
- Para posiciones largas:
- Cierre un tercio del volumen inicial cuando el cierre de la vela supere el medio EMA (
EmaMediumPeriod).
- Cierre la mitad de la posición restante cuando el máximo de la vela atraviese el promedio de los valores
SmaPeriod y EmaLongPeriod.
- Para las posiciones cortas, las reglas se reflejan con el cierre de la vela por debajo del medio EMA y el mínimo de la vela por debajo del promedio compuesto.
Las órdenes de protección se verifican antes de escalar para garantizar que las paradas bruscas o los objetivos siempre tengan prioridad.
Parámetros
| Parámetro |
Predeterminado |
Descripción |
StopLossBars |
6 |
Número de velas históricas para la búsqueda de swing de stop-loss. |
TakeProfitBars |
20 |
Tamaño de bloque utilizado por el algoritmo recursivo de obtención de beneficios. |
OffsetPoints |
10 |
Se agregaron puntos adicionales al precio del stop-loss. |
MacdFastPeriod |
5 |
Longitud rápida EMA del indicador MACD. |
MacdSlowPeriod |
13 |
Longitud lenta de EMA del indicador MACD. |
MacdSignalPeriod |
1 |
Longitud de la señal EMA del indicador MACD. |
BearishThreshold |
0.0045 |
Nivel positivo MACD que arma configuraciones cortas. |
BullishThreshold |
-0.0045 |
Nivel negativo MACD que arma configuraciones largas. |
OrderVolume |
1 |
Volumen por orden de mercado. |
EmaShortPeriod |
7 |
EMA rápida utilizado en la primera salida parcial. |
EmaMediumPeriod |
21 |
Medio EMA utilizado en filtros y salidas parciales. |
SmaPeriod |
98 |
SMA utilizado dentro del promedio de salida compuesto. |
EmaLongPeriod |
365 |
EMA largo combinado con el SMA para la segunda salida parcial. |
ProfitThreshold |
5 |
Beneficio flotante mínimo (en unidades monetarias) antes del escalamiento horizontal. |
CandleType |
plazo de 1 hora |
Serie de velas procesadas por la estrategia. |
Todos los parámetros están expuestos a través de StrategyParam<T> y admiten la optimización cuando corresponda.
Notas de implementación
- La estrategia se basa exclusivamente en enlaces
SubscribeCandles de alto nivel. No inserta indicadores en la colección Indicators, siguiendo las pautas del proyecto.
- El historial de MACD se almacena utilizando un registro de desplazamiento compacto de tres valores (
_macdPrev1..3) para imitar el acceso a iMACD(..., shift).
- Los niveles de precios protectores se registran como decimales; cuando las velas alcanzan un tope o un objetivo, la estrategia cierra toda la posición con órdenes de mercado y reinicia la máquina de estado interna.
- El PnL flotante se estima utilizando
PriceStep/StepPrice para que el umbral de salida parcial permanezca constante independientemente de la escala de precios del instrumento.
- Los buffers de velas para máximos y mínimos tienen un límite de 1000 elementos, lo que es suficiente para los parámetros predeterminados pero evita un crecimiento descontrolado.
Uso
- Cree una instancia de
MacdPatternTraderV01Strategy, asigne la seguridad, la cartera y el conector deseados.
- Opcionalmente, ajuste parámetros como
CandleType, StopLossBars o OrderVolume para adaptarlos al instrumento negociado.
- Iniciar la estrategia; se suscribirá a la serie de velas configurada, dibujará MACD e intercambiará marcadores en el gráfico y administrará las órdenes automáticamente.
La estrategia contiene extensos comentarios en línea que describen cada bloque traducido para facilitar el mantenimiento y una mayor personalización.
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>
/// MACD pattern strategy converted from the FORTRADER "MacdPatternTraderv01" expert advisor.
/// </summary>
public class MacdPatternTraderV01Strategy : Strategy
{
private readonly StrategyParam<int> _stopLossBars;
private readonly StrategyParam<int> _takeProfitBars;
private readonly StrategyParam<int> _offsetPoints;
private readonly StrategyParam<int> _historyLimit;
private readonly StrategyParam<int> _macdFastPeriod;
private readonly StrategyParam<int> _macdSlowPeriod;
private readonly StrategyParam<int> _macdSignalPeriod;
private readonly StrategyParam<decimal> _bearishThreshold;
private readonly StrategyParam<decimal> _bullishThreshold;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _emaShortPeriod;
private readonly StrategyParam<int> _emaMediumPeriod;
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<int> _emaLongPeriod;
private readonly StrategyParam<decimal> _profitThreshold;
private readonly StrategyParam<DataType> _candleType;
private MACD _macd = null!;
private ExponentialMovingAverage _emaShort = null!;
private ExponentialMovingAverage _emaMedium = null!;
private SimpleMovingAverage _sma = null!;
private ExponentialMovingAverage _emaLong = null!;
private decimal? _macdPrev1;
private decimal? _macdPrev2;
private decimal? _macdPrev3;
private bool _bearishArmed;
private bool _bullishArmed;
private bool _pendingSell;
private bool _pendingBuy;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal? _entryPrice;
private decimal? _initialVolume;
private int _buyPartialStage;
private int _sellPartialStage;
private readonly List<decimal> _recentLows = new();
private readonly List<decimal> _recentHighs = new();
/// <summary>
/// Number of finished candles used to determine the stop-loss reference high/low.
/// </summary>
public int StopLossBars { get => _stopLossBars.Value; set => _stopLossBars.Value = value; }
/// <summary>
/// Number of candles used by the recursive take-profit search.
/// </summary>
public int TakeProfitBars { get => _takeProfitBars.Value; set => _takeProfitBars.Value = value; }
/// <summary>
/// Offset in points added to the calculated stop level.
/// </summary>
public int OffsetPoints { get => _offsetPoints.Value; set => _offsetPoints.Value = value; }
/// <summary>
/// Number of historical candles stored for swing detection.
/// </summary>
public int HistoryLimit { get => _historyLimit.Value; set => _historyLimit.Value = value; }
/// <summary>
/// Fast EMA period for the MACD indicator.
/// </summary>
public int MacdFastPeriod { get => _macdFastPeriod.Value; set => _macdFastPeriod.Value = value; }
/// <summary>
/// Slow EMA period for the MACD indicator.
/// </summary>
public int MacdSlowPeriod { get => _macdSlowPeriod.Value; set => _macdSlowPeriod.Value = value; }
/// <summary>
/// Signal EMA period for the MACD indicator.
/// </summary>
public int MacdSignalPeriod { get => _macdSignalPeriod.Value; set => _macdSignalPeriod.Value = value; }
/// <summary>
/// Positive MACD level that arms the bearish hook setup.
/// </summary>
public decimal BearishThreshold { get => _bearishThreshold.Value; set => _bearishThreshold.Value = value; }
/// <summary>
/// Negative MACD level that arms the bullish hook setup.
/// </summary>
public decimal BullishThreshold { get => _bullishThreshold.Value; set => _bullishThreshold.Value = value; }
/// <summary>
/// Volume for every market order.
/// </summary>
public decimal OrderVolume { get => _orderVolume.Value; set => _orderVolume.Value = value; }
/// <summary>
/// EMA period used by the first partial exit condition.
/// </summary>
public int EmaShortPeriod { get => _emaShortPeriod.Value; set => _emaShortPeriod.Value = value; }
/// <summary>
/// EMA period used by the crossover filter and the the first partial exit.
/// </summary>
public int EmaMediumPeriod { get => _emaMediumPeriod.Value; set => _emaMediumPeriod.Value = value; }
/// <summary>
/// SMA period participating in the second partial exit.
/// </summary>
public int SmaPeriod { get => _smaPeriod.Value; set => _smaPeriod.Value = value; }
/// <summary>
/// Long-term EMA period used inside the composite exit average.
/// </summary>
public int EmaLongPeriod { get => _emaLongPeriod.Value; set => _emaLongPeriod.Value = value; }
/// <summary>
/// Minimal floating profit (in currency) required before partial exits are allowed.
/// </summary>
public decimal ProfitThreshold { get => _profitThreshold.Value; set => _profitThreshold.Value = value; }
/// <summary>
/// Candle data type used by the strategy.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initializes a new instance of <see cref="MacdPatternTraderV01Strategy"/>.
/// </summary>
public MacdPatternTraderV01Strategy()
{
_stopLossBars = Param(nameof(StopLossBars), 6)
.SetGreaterThanZero()
.SetDisplay("Stop-Loss Bars", "Bars used to determine the stop-loss swing", "Risk")
;
_takeProfitBars = Param(nameof(TakeProfitBars), 20)
.SetGreaterThanZero()
.SetDisplay("Take-Profit Bars", "Bars per block for the recursive take-profit", "Risk")
;
_offsetPoints = Param(nameof(OffsetPoints), 10)
.SetGreaterThanZero()
.SetDisplay("Stop Offset", "Additional points added to the stop-loss", "Risk");
_historyLimit = Param(nameof(HistoryLimit), 1000)
.SetGreaterThanZero()
.SetDisplay("History Limit", "Number of recent candles stored for swing detection", "Risk");
_macdFastPeriod = Param(nameof(MacdFastPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "Fast EMA length for MACD", "Indicators");
_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "Slow EMA length for MACD", "Indicators");
_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 1)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "Signal EMA length for MACD", "Indicators");
_bearishThreshold = Param(nameof(BearishThreshold), 50m)
.SetDisplay("Bearish Threshold", "Positive MACD level that arms short trades", "Signals");
_bullishThreshold = Param(nameof(BullishThreshold), -50m)
.SetDisplay("Bullish Threshold", "Negative MACD level that arms long trades", "Signals");
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Trade volume for every market order", "General");
_emaShortPeriod = Param(nameof(EmaShortPeriod), 7)
.SetGreaterThanZero()
.SetDisplay("EMA Short", "Short EMA period for position management", "Indicators");
_emaMediumPeriod = Param(nameof(EmaMediumPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("EMA Medium", "Medium EMA period for filters", "Indicators");
_smaPeriod = Param(nameof(SmaPeriod), 98)
.SetGreaterThanZero()
.SetDisplay("SMA Period", "SMA period used in the composite exit", "Indicators");
_emaLongPeriod = Param(nameof(EmaLongPeriod), 365)
.SetGreaterThanZero()
.SetDisplay("EMA Long", "Long EMA period used in the composite exit", "Indicators");
_profitThreshold = Param(nameof(ProfitThreshold), 5m)
.SetDisplay("Profit Threshold", "Minimum floating profit before scaling out", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Source series for the strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macdPrev1 = null;
_macdPrev2 = null;
_macdPrev3 = null;
_bearishArmed = false;
_bullishArmed = false;
_pendingSell = false;
_pendingBuy = false;
_stopPrice = null;
_takePrice = null;
_entryPrice = null;
_initialVolume = null;
_buyPartialStage = 0;
_sellPartialStage = 0;
_recentLows.Clear();
_recentHighs.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = OrderVolume;
_macd = new MACD();
_macd.ShortMa.Length = MacdFastPeriod;
_macd.LongMa.Length = MacdSlowPeriod;
_emaShort = new EMA { Length = EmaShortPeriod };
_emaMedium = new EMA { Length = EmaMediumPeriod };
_sma = new SMA { Length = SmaPeriod };
_emaLong = new EMA { Length = EmaLongPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_macd, _emaShort, _emaMedium, _sma, _emaLong, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _macd);
DrawIndicator(area, _emaMedium);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal macdLine, decimal emaShortValue, decimal emaMediumValue, decimal smaValue, decimal emaLongValue)
{
if (candle.State != CandleStates.Finished)
return;
// Check protective targets before generating new signals.
HandleProtectiveExits(candle);
// Try scaling out according to the EMA/SMA management rules.
HandlePartialExits(candle, emaMediumValue, smaValue, emaLongValue);
if (!_macd.IsFormed)
{
// Keep collecting MACD values while the indicator is not formed yet.
UpdateMacdHistory(macdLine);
StoreCandleExtremes(candle);
return;
}
// Store the freshly calculated MACD value for pattern detection.
UpdateMacdHistory(macdLine);
if (_macdPrev1 is null || _macdPrev2 is null || _macdPrev3 is null)
{
StoreCandleExtremes(candle);
return;
}
var macdCurr = _macdPrev1.Value;
var macdLast = _macdPrev2.Value;
var macdLast3 = _macdPrev3.Value;
// A new bullish MACD extreme arms the potential bearish hook sequence.
if (macdCurr > BearishThreshold)
_bearishArmed = true;
if (macdCurr < 0m)
_bearishArmed = false;
if (macdCurr < BearishThreshold && macdCurr < macdLast && macdLast > macdLast3 && _bearishArmed && macdCurr > 0m && macdLast3 < BearishThreshold)
{
_pendingSell = true;
}
// Execute a short entry once all bearish requirements are met.
if (_pendingSell)
{
TryEnterShort(candle);
}
// A deep negative MACD swing arms the bullish hook setup.
if (macdCurr < BullishThreshold)
_bullishArmed = true;
if (macdCurr > 0m)
_bullishArmed = false;
if (macdCurr > BullishThreshold && macdCurr < 0m && macdCurr > macdLast && macdLast < macdLast3 && _bullishArmed && macdLast3 > BullishThreshold)
{
_pendingBuy = true;
}
// Execute a long entry once all bullish requirements are satisfied.
if (_pendingBuy)
{
TryEnterLong(candle);
}
StoreCandleExtremes(candle);
}
private void HandleProtectiveExits(ICandleMessage candle)
{
if (Position > 0)
{
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Position);
ResetPositionState();
return;
}
if (_takePrice is decimal take && candle.HighPrice >= take)
{
SellMarket(Position);
ResetPositionState();
return;
}
}
else if (Position < 0)
{
var volume = Math.Abs(Position);
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(volume);
ResetPositionState();
return;
}
if (_takePrice is decimal take && candle.LowPrice <= take)
{
BuyMarket(volume);
ResetPositionState();
}
}
}
private void HandlePartialExits(ICandleMessage candle, decimal emaMediumValue, decimal smaValue, decimal emaLongValue)
{
if (Position == 0 || _entryPrice is null)
return;
// Evaluate the floating PnL to decide whether partial exits are allowed.
var profit = CalculateFloatingProfit(candle.ClosePrice);
if (profit < ProfitThreshold)
return;
if (Position > 0)
{
if (_buyPartialStage == 0 && candle.ClosePrice > emaMediumValue)
{
var volume = GetInitialPortionVolume(3m);
if (volume > 0m)
{
SellMarket(Math.Min(volume, Position));
_buyPartialStage = 1;
}
}
else if (_buyPartialStage == 1)
{
// Combine the slow averages to reproduce the MQL composite threshold.
var composite = (smaValue + emaLongValue) / 2m;
if (candle.HighPrice > composite)
{
var volume = Math.Abs(Position) / 2m;
if (volume > 0m)
{
SellMarket(volume);
_buyPartialStage = 2;
}
}
}
}
else if (Position < 0)
{
var absPosition = Math.Abs(Position);
if (_sellPartialStage == 0 && candle.ClosePrice < emaMediumValue)
{
var volume = GetInitialPortionVolume(3m);
if (volume > 0m)
{
BuyMarket(Math.Min(volume, absPosition));
_sellPartialStage = 1;
}
}
else if (_sellPartialStage == 1)
{
var composite = (smaValue + emaLongValue) / 2m;
if (candle.LowPrice < composite)
{
var volume = absPosition / 2m;
if (volume > 0m)
{
BuyMarket(volume);
_sellPartialStage = 2;
}
}
}
}
}
private decimal GetInitialPortionVolume(decimal divider)
{
if (_initialVolume is null || _initialVolume.Value <= 0m)
return 0m;
return _initialVolume.Value / divider;
}
private decimal CalculateFloatingProfit(decimal price)
{
if (_entryPrice is null || Security is null)
return 0m;
var positionVolume = Math.Abs(Position);
if (positionVolume == 0m)
return 0m;
var priceStep = Security.PriceStep ?? 0m;
var stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? priceStep;
if (priceStep <= 0m || stepPrice <= 0m)
return 0m;
var diff = price - _entryPrice.Value;
var steps = diff / priceStep;
var money = steps * stepPrice * positionVolume;
return Position > 0 ? money : -money;
}
private void TryEnterShort(ICandleMessage candle)
{
_pendingSell = false;
_bearishArmed = false;
if (Position < 0)
return;
if (OrderVolume <= 0m)
return;
// Replicate the MQL stop-loss that scans past highs plus the configured offset.
var stop = CalculateStopPrice(false);
// Compute the recursive take-profit using the finished candle lows.
var take = CalculateTakePrice(false, candle);
if (stop is null || take is null)
return;
SellMarket(OrderVolume);
_stopPrice = stop;
_takePrice = take;
_entryPrice = candle.ClosePrice;
_initialVolume = OrderVolume;
_sellPartialStage = 0;
_buyPartialStage = 0;
}
private void TryEnterLong(ICandleMessage candle)
{
_pendingBuy = false;
_bullishArmed = false;
if (Position > 0)
return;
if (OrderVolume <= 0m)
return;
// Derive the long stop-loss from the recent swing lows.
var stop = CalculateStopPrice(true);
// Search for the layered bullish target via the recursive high scan.
var take = CalculateTakePrice(true, candle);
if (stop is null || take is null)
return;
BuyMarket(OrderVolume);
_stopPrice = stop;
_takePrice = take;
_entryPrice = candle.ClosePrice;
_initialVolume = OrderVolume;
_buyPartialStage = 0;
_sellPartialStage = 0;
}
private decimal? CalculateStopPrice(bool isLong)
{
var length = StopLossBars;
// Invalid configuration means the level cannot be evaluated.
if (length <= 0)
return null;
if (isLong)
{
// Not enough historical lows to reproduce the original lookback.
if (_recentLows.Count < length)
return null;
// Manual iteration avoids allocating additional buffers for extrema search.
var min = decimal.MaxValue;
for (var i = _recentLows.Count - length; i < _recentLows.Count; i++)
{
var value = _recentLows[i];
if (value < min)
min = value;
}
var offset = GetOffsetPrice();
return min - offset;
}
else
{
if (_recentHighs.Count < length)
return null;
// Mirror the same manual search for the highest value.
var max = decimal.MinValue;
for (var i = _recentHighs.Count - length; i < _recentHighs.Count; i++)
{
var value = _recentHighs[i];
if (value > max)
max = value;
}
var offset = GetOffsetPrice();
return max + offset;
}
}
private decimal? CalculateTakePrice(bool isLong, ICandleMessage candle)
{
var length = TakeProfitBars;
if (length <= 0)
return null;
var totalWithCurrent = _recentHighs.Count + 1;
if (totalWithCurrent < length)
return null;
// Iterate over consecutive blocks until the extrema stop improving.
decimal? best = null;
var segment = 0;
while (true)
{
// Evaluate the next block by combining historical data with the current candle.
var extreme = isLong
? GetSegmentExtreme(_recentHighs, candle.HighPrice, length, segment, false)
: GetSegmentExtreme(_recentLows, candle.LowPrice, length, segment, true);
if (extreme is null)
break;
if (best is null)
{
best = extreme;
segment++;
continue;
}
if (isLong)
{
if (extreme.Value > best.Value)
{
best = extreme;
segment++;
continue;
}
}
else
{
if (extreme.Value < best.Value)
{
best = extreme;
segment++;
continue;
}
}
break;
}
return best;
}
private decimal? GetSegmentExtreme(List<decimal> source, decimal currentValue, int length, int segmentIndex, bool isMin)
{
// Treat the finished candle as an extra sample appended to the stored history.
var total = source.Count + 1;
var end = total - 1 - segmentIndex * length;
var start = end - length + 1;
if (start < 0)
return null;
decimal extreme = isMin ? decimal.MaxValue : decimal.MinValue;
for (var i = start; i <= end; i++)
{
var value = i == source.Count ? currentValue : source[i];
if (isMin)
{
if (value < extreme)
extreme = value;
}
else
{
if (value > extreme)
extreme = value;
}
}
return extreme;
}
private decimal GetOffsetPrice()
{
// Convert the configured offset from points into an absolute price distance.
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
return 0m;
return OffsetPoints * priceStep;
}
private void StoreCandleExtremes(ICandleMessage candle)
{
_recentLows.Add(candle.LowPrice);
_recentHighs.Add(candle.HighPrice);
if (_recentLows.Count > HistoryLimit)
_recentLows.RemoveAt(0);
if (_recentHighs.Count > HistoryLimit)
_recentHighs.RemoveAt(0);
}
private void ResetPositionState()
{
_stopPrice = null;
_takePrice = null;
_entryPrice = null;
_initialVolume = null;
_buyPartialStage = 0;
_sellPartialStage = 0;
}
private void UpdateMacdHistory(decimal macdLine)
{
_macdPrev3 = _macdPrev2;
_macdPrev2 = _macdPrev1;
_macdPrev1 = macdLine;
}
}
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.Indicators import MovingAverageConvergenceDivergence, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class macd_pattern_trader_v01_strategy(Strategy):
"""
MACD hook pattern trader: detects MACD hook reversals for entries.
Simplified from the full C# version (no partial exits/volume management).
"""
def __init__(self):
super(macd_pattern_trader_v01_strategy, self).__init__()
self._macd_fast = self.Param("MacdFastPeriod", 5).SetDisplay("MACD Fast", "Fast EMA", "Indicators")
self._macd_slow = self.Param("MacdSlowPeriod", 13).SetDisplay("MACD Slow", "Slow EMA", "Indicators")
self._bearish_threshold = self.Param("BearishThreshold", 50.0).SetDisplay("Bearish Threshold", "Arms short trades", "Signals")
self._bullish_threshold = self.Param("BullishThreshold", -50.0).SetDisplay("Bullish Threshold", "Arms long trades", "Signals")
self._cooldown_bars = self.Param("CooldownBars", 10).SetDisplay("Cooldown", "Bars between signals", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._macd_prev1 = None
self._macd_prev2 = None
self._macd_prev3 = None
self._bearish_armed = False
self._bullish_armed = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_pattern_trader_v01_strategy, self).OnReseted()
self._macd_prev1 = None
self._macd_prev2 = None
self._macd_prev3 = None
self._bearish_armed = False
self._bullish_armed = False
self._cooldown = 0
def OnStarted2(self, time):
super(macd_pattern_trader_v01_strategy, self).OnStarted2(time)
macd = MovingAverageConvergenceDivergence()
macd.ShortMa.Length = self._macd_fast.Value
macd.LongMa.Length = self._macd_slow.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(macd, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, macd)
self.DrawOwnTrades(area)
def _process_candle(self, candle, macd_val):
if candle.State != CandleStates.Finished:
return
macd_line = float(macd_val)
self._macd_prev3 = self._macd_prev2
self._macd_prev2 = self._macd_prev1
self._macd_prev1 = macd_line
if self._macd_prev3 is None:
return
if self._cooldown > 0:
self._cooldown -= 1
return
curr = self._macd_prev1
last = self._macd_prev2
last3 = self._macd_prev3
bt = float(self._bearish_threshold.Value)
blt = float(self._bullish_threshold.Value)
if curr > bt:
self._bearish_armed = True
if curr < 0:
self._bearish_armed = False
if self._bearish_armed and curr < bt and curr < last and last > last3 and curr > 0 and last3 < bt:
if self.Position >= 0:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
self._bearish_armed = False
return
if curr < blt:
self._bullish_armed = True
if curr > 0:
self._bullish_armed = False
if self._bullish_armed and curr > blt and curr < 0 and curr > last and last < last3 and last3 > blt:
if self.Position <= 0:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
self._bullish_armed = False
def CreateClone(self):
return macd_pattern_trader_v01_strategy()