Estratégia IMACD Sniper
IMACD Sniper combina cruzamentos do MACD com um filtro de tendência EMA, confirmação de volume e padrões de velas fortes. O take profit e o stop loss dinâmicos são baseados no intervalo médio recente.
Detalhes
- Dados: Velas de preço.
- Critérios de entrada:
- Comprado: A linha MACD cruza acima da linha de sinal, preço acima da EMA, delta do MACD > delta mínimo, ambas as linhas longe de zero, volume acima da média, vela altista forte.
- Vendido: A linha MACD cruza abaixo da linha de sinal, preço abaixo da EMA, delta do MACD > delta mínimo, ambas as linhas longe de zero, volume acima da média, vela baixista forte.
- Critérios de saída: Cruzamento oposto do MACD ou atingir take profit / stop loss.
- Stops: Take profit e stop loss dinâmicos baseados no intervalo médio.
- Valores padrão:
FastLength= 12SlowLength= 26SignalLength= 9MacdDeltaMin= 0.03MacdZeroLimit= 0.05RangeLength= 14RangeMultiplierTp= 4.0RangeMultiplierSl= 1.5EmaLength= 20CandleType= tf(1m)
- Filtros:
- Categoria: Tendência
- Direção: Comprado e Vendido
- Indicadores: MACD, EMA, Volume
- Stops: Sim
- Complexidade: Intermediário
- Período: Qualquer
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Médio
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// IMACD Sniper strategy.
/// A long opens when the MACD line crosses above its signal line with the close above the EMA, the gap between the lines above MacdDeltaMin,
/// both lines at least MacdZeroLimit away from zero, the candle volume above its RangeLength average and a strong bullish candle
/// (body at least half of its range). Shorts mirror these rules. An opposite MACD cross closes the position, and the take profit and
/// stop loss sit RangeMultiplierTp and RangeMultiplierSl average candle ranges away from the entry.
/// </summary>
public class ImacdSniperStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<decimal> _macdDeltaMin;
private readonly StrategyParam<decimal> _macdZeroLimit;
private readonly StrategyParam<int> _rangeLength;
private readonly StrategyParam<decimal> _rangeMultiplierTp;
private readonly StrategyParam<decimal> _rangeMultiplierSl;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _rangeSma;
private SimpleMovingAverage _volumeSma;
private decimal? _prevMacd;
private decimal? _prevSignal;
private decimal? _takePrice;
private decimal? _stopPrice;
/// <summary>
/// MACD fast length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// MACD slow length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// MACD signal length.
/// </summary>
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
/// <summary>
/// Minimum distance between MACD and signal lines for entry.
/// </summary>
public decimal MacdDeltaMin
{
get => _macdDeltaMin.Value;
set => _macdDeltaMin.Value = value;
}
/// <summary>
/// Minimum distance of both MACD lines from zero.
/// </summary>
public decimal MacdZeroLimit
{
get => _macdZeroLimit.Value;
set => _macdZeroLimit.Value = value;
}
/// <summary>
/// Candles for the average range and average volume.
/// </summary>
public int RangeLength
{
get => _rangeLength.Value;
set => _rangeLength.Value = value;
}
/// <summary>
/// Take-profit multiplier of the average range.
/// </summary>
public decimal RangeMultiplierTp
{
get => _rangeMultiplierTp.Value;
set => _rangeMultiplierTp.Value = value;
}
/// <summary>
/// Stop-loss multiplier of the average range.
/// </summary>
public decimal RangeMultiplierSl
{
get => _rangeMultiplierSl.Value;
set => _rangeMultiplierSl.Value = value;
}
/// <summary>
/// EMA length.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ImacdSniperStrategy()
{
_fastLength = Param(nameof(FastLength), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast Length", "MACD fast period", "MACD")
.SetOptimize(6, 24, 2);
_slowLength = Param(nameof(SlowLength), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow Length", "MACD slow period", "MACD")
.SetOptimize(20, 40, 2);
_signalLength = Param(nameof(SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal Length", "MACD signal smoothing", "MACD")
.SetOptimize(5, 15, 1);
_macdDeltaMin = Param(nameof(MacdDeltaMin), 0.03m)
.SetNotNegative()
.SetDisplay("Min MACD Delta", "Minimum distance between MACD and signal for entry", "Filters");
_macdZeroLimit = Param(nameof(MacdZeroLimit), 0.05m)
.SetNotNegative()
.SetDisplay("MACD Zero Limit", "Minimum distance of both lines from zero for entry", "Filters");
_rangeLength = Param(nameof(RangeLength), 14)
.SetGreaterThanZero()
.SetDisplay("Range Length", "Candles for the average range and volume", "Risk")
.SetOptimize(5, 30, 1);
_rangeMultiplierTp = Param(nameof(RangeMultiplierTp), 4m)
.SetNotNegative()
.SetDisplay("Range Multiplier TP", "Take profit in average ranges", "Risk")
.SetOptimize(1m, 6m, 1m);
_rangeMultiplierSl = Param(nameof(RangeMultiplierSl), 1.5m)
.SetNotNegative()
.SetDisplay("Range Multiplier SL", "Stop loss in average ranges", "Risk")
.SetOptimize(1m, 3m, 0.5m);
_emaLength = Param(nameof(EmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "Trend EMA period", "Trend")
.SetOptimize(10, 50, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rangeSma = null;
_volumeSma = null;
ResetState();
}
private void ResetState()
{
_prevMacd = null;
_prevSignal = null;
_takePrice = null;
_stopPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
_rangeSma = new SimpleMovingAverage { Length = RangeLength };
_volumeSma = new SimpleMovingAverage { Length = RangeLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd = { ShortMa = { Length = FastLength }, LongMa = { Length = SlowLength } },
SignalMa = { Length = SignalLength }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
var macdArea = CreateChartArea();
if (macdArea != null)
DrawIndicator(macdArea, macd);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue emaValue)
{
if (candle.State != CandleStates.Finished)
return;
var range = candle.HighPrice - candle.LowPrice;
var rangeValue = _rangeSma.Process(new DecimalIndicatorValue(_rangeSma, range, candle.OpenTime) { IsFinal = true });
var volumeValue = _volumeSma.Process(new DecimalIndicatorValue(_volumeSma, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
if (!macdValue.IsFormed || macdValue is not MovingAverageConvergenceDivergenceSignalValue { Macd: decimal macd, Signal: decimal signal })
return;
var prevMacd = _prevMacd;
var prevSignal = _prevSignal;
_prevMacd = macd;
_prevSignal = signal;
if (CheckProtection(candle))
return;
if (prevMacd is not decimal pm || prevSignal is not decimal ps)
return;
if (!emaValue.IsFormed || !_rangeSma.IsFormed || !_volumeSma.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ema = emaValue.GetValue<decimal>();
var avgRange = rangeValue.GetValue<decimal>();
var avgVolume = volumeValue.GetValue<decimal>();
var close = candle.ClosePrice;
var crossUp = pm <= ps && macd > signal;
var crossDown = pm >= ps && macd < signal;
var deltaOk = Math.Abs(macd - signal) > MacdDeltaMin;
var farFromZero = Math.Abs(macd) > MacdZeroLimit && Math.Abs(signal) > MacdZeroLimit;
var volumeOk = candle.TotalVolume > avgVolume;
var body = Math.Abs(close - candle.OpenPrice);
var strongBody = range > 0 && body * 2 >= range;
var longEntry = crossUp && close > ema && deltaOk && farFromZero && volumeOk && strongBody && close > candle.OpenPrice;
var shortEntry = crossDown && close < ema && deltaOk && farFromZero && volumeOk && strongBody && close < candle.OpenPrice;
if (longEntry && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
SetLevels(close, avgRange, true);
}
else if (shortEntry && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
SetLevels(close, avgRange, false);
}
else if (Position > 0 && crossDown)
{
SellMarket(Position);
ClearLevels();
}
else if (Position < 0 && crossUp)
{
BuyMarket(-Position);
ClearLevels();
}
}
private void SetLevels(decimal entry, decimal avgRange, bool isLong)
{
var take = avgRange * RangeMultiplierTp;
var stop = avgRange * RangeMultiplierSl;
var sign = isLong ? 1m : -1m;
_takePrice = take > 0 ? entry + sign * take : null;
_stopPrice = stop > 0 ? entry - sign * stop : null;
}
private void ClearLevels()
{
_takePrice = null;
_stopPrice = null;
}
// Returns true when the take profit or stop loss closed the position on this candle.
private bool CheckProtection(ICandleMessage candle)
{
if (Position == 0)
{
ClearLevels();
return false;
}
if (Position > 0)
{
if ((_stopPrice is decimal sl && candle.LowPrice <= sl) || (_takePrice is decimal tp && candle.HighPrice >= tp))
{
SellMarket(Position);
ClearLevels();
return true;
}
}
else
{
if ((_stopPrice is decimal sl && candle.HighPrice >= sl) || (_takePrice is decimal tp && candle.LowPrice <= tp))
{
BuyMarket(-Position);
ClearLevels();
return true;
}
}
return false;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, MovingAverageConvergenceDivergenceSignal, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class imacd_sniper_strategy(Strategy):
"""
IMACD Sniper strategy.
A long opens when the MACD line crosses above its signal line with the close above the EMA, the gap between the lines above MacdDeltaMin,
both lines at least MacdZeroLimit away from zero, the candle volume above its RangeLength average and a strong bullish candle
(body at least half of its range). Shorts mirror these rules. An opposite MACD cross closes the position, and the take profit and
stop loss sit RangeMultiplierTp and RangeMultiplierSl average candle ranges away from the entry.
"""
def __init__(self):
super(imacd_sniper_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12).SetGreaterThanZero().SetDisplay("MACD Fast Length", "MACD fast period", "MACD")
self._slow_length = self.Param("SlowLength", 26).SetGreaterThanZero().SetDisplay("MACD Slow Length", "MACD slow period", "MACD")
self._signal_length = self.Param("SignalLength", 9).SetGreaterThanZero().SetDisplay("MACD Signal Length", "MACD signal smoothing", "MACD")
self._macd_delta_min = self.Param("MacdDeltaMin", 0.03).SetNotNegative().SetDisplay("Min MACD Delta", "Minimum distance between MACD and signal for entry", "Filters")
self._macd_zero_limit = self.Param("MacdZeroLimit", 0.05).SetNotNegative().SetDisplay("MACD Zero Limit", "Minimum distance of both lines from zero for entry", "Filters")
self._range_length = self.Param("RangeLength", 14).SetGreaterThanZero().SetDisplay("Range Length", "Candles for the average range and volume", "Risk")
self._range_multiplier_tp = self.Param("RangeMultiplierTp", 4.0).SetNotNegative().SetDisplay("Range Multiplier TP", "Take profit in average ranges", "Risk")
self._range_multiplier_sl = self.Param("RangeMultiplierSl", 1.5).SetNotNegative().SetDisplay("Range Multiplier SL", "Stop loss in average ranges", "Risk")
self._ema_length = self.Param("EmaLength", 20).SetGreaterThanZero().SetDisplay("EMA Length", "Trend EMA period", "Trend")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles", "General")
self._range_sma = None
self._volume_sma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_macd = None
self._prev_signal = None
self._take_price = None
self._stop_price = None
def OnReseted(self):
super(imacd_sniper_strategy, self).OnReseted()
self._range_sma = None
self._volume_sma = None
self._reset_state()
def OnStarted2(self, time):
super(imacd_sniper_strategy, self).OnStarted2(time)
self._reset_state()
self._range_sma = SimpleMovingAverage()
self._range_sma.Length = self._range_length.Value
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = self._range_length.Value
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._fast_length.Value
macd.Macd.LongMa.Length = self._slow_length.Value
macd.SignalMa.Length = self._signal_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
macd_area = self.CreateChartArea()
if macd_area is not None:
self.DrawIndicator(macd_area, macd)
def _process_candle(self, candle, macd_value, ema_value):
if candle.State != CandleStates.Finished:
return
candle_range = candle.HighPrice - candle.LowPrice
range_value = process_value(self._range_sma, candle_range, candle.OpenTime, True)
volume_value = process_value(self._volume_sma, candle.TotalVolume, candle.OpenTime, True)
if not macd_value.IsFormed or macd_value.Macd is None or macd_value.Signal is None:
return
macd = macd_value.Macd
signal = macd_value.Signal
prev_macd = self._prev_macd
prev_signal = self._prev_signal
self._prev_macd = macd
self._prev_signal = signal
if self._check_protection(candle):
return
if prev_macd is None or prev_signal is None:
return
if not ema_value.IsFormed or not self._range_sma.IsFormed or not self._volume_sma.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ema = ema_value.GetValue[Decimal](None)
avg_range = to_decimal(range_value)
avg_volume = to_decimal(volume_value)
close = candle.ClosePrice
open_price = candle.OpenPrice
cross_up = prev_macd <= prev_signal and macd > signal
cross_down = prev_macd >= prev_signal and macd < signal
delta_ok = abs(macd - signal) > Decimal(self._macd_delta_min.Value)
zero_limit = Decimal(self._macd_zero_limit.Value)
far_from_zero = abs(macd) > zero_limit and abs(signal) > zero_limit
volume_ok = candle.TotalVolume > avg_volume
body = abs(close - open_price)
strong_body = candle_range > 0 and body * 2 >= candle_range
long_entry = cross_up and close > ema and delta_ok and far_from_zero and volume_ok and strong_body and close > open_price
short_entry = cross_down and close < ema and delta_ok and far_from_zero and volume_ok and strong_body and close < open_price
if long_entry and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._set_levels(close, avg_range, True)
elif short_entry and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._set_levels(close, avg_range, False)
elif self.Position > 0 and cross_down:
self.SellMarket(self.Position)
self._clear_levels()
elif self.Position < 0 and cross_up:
self.BuyMarket(-self.Position)
self._clear_levels()
def _set_levels(self, entry, avg_range, is_long):
take = avg_range * Decimal(self._range_multiplier_tp.Value)
stop = avg_range * Decimal(self._range_multiplier_sl.Value)
if is_long:
self._take_price = entry + take if take > 0 else None
self._stop_price = entry - stop if stop > 0 else None
else:
self._take_price = entry - take if take > 0 else None
self._stop_price = entry + stop if stop > 0 else None
def _clear_levels(self):
self._take_price = None
self._stop_price = None
# Returns True when the take profit or stop loss closed the position on this candle.
def _check_protection(self, candle):
if self.Position == 0:
self._clear_levels()
return False
sl = self._stop_price
tp = self._take_price
if self.Position > 0:
if (sl is not None and candle.LowPrice <= sl) or (tp is not None and candle.HighPrice >= tp):
self.SellMarket(self.Position)
self._clear_levels()
return True
else:
if (sl is not None and candle.HighPrice >= sl) or (tp is not None and candle.LowPrice <= tp):
self.BuyMarket(-self.Position)
self._clear_levels()
return True
return False
def CreateClone(self):
return imacd_sniper_strategy()