Martingale with MACD and KDJ Strategy
This strategy enters trades when both the MACD line and the KDJ %K line cross their signal lines in the same direction. It pyramids positions using a martingale approach, adding when price moves against the trade by a configured percent and then rebounds.
Positions are closed when a take profit, stop loss, or trailing stop condition is met.
Details
- Entry: MACD line and KDJ %K line cross their signal lines in the same direction.
- Additions: Up to
Max Additionstimes when price moves byAdd Position Percentand rebounds byRebound Percent. Each addition size is multiplied byAdd Multiplier. - Exit: Close on
Take Profit Trigger,Stop Loss Percentor trailing stop hit. - Direction: Long and short.
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;
public class MartingaleWithMacdKdjOpeningConditionsStrategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergence _macd;
private decimal _prevMacd;
private bool _hasPrev;
private decimal _entryPrice;
private int _barsFromTrade;
public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MartingaleWithMacdKdjOpeningConditionsStrategy()
{
_takeProfitPercent = Param(nameof(TakeProfitPercent), 4m);
_stopLossPercent = Param(nameof(StopLossPercent), 8m);
_cooldownBars = Param(nameof(CooldownBars), 30);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame());
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macd = null;
_prevMacd = 0m;
_hasPrev = false;
_entryPrice = 0m;
_barsFromTrade = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_macd = new MovingAverageConvergenceDivergence();
_prevMacd = 0;
_hasPrev = false;
_entryPrice = 0;
_barsFromTrade = CooldownBars;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_macd, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal macd)
{
if (candle.State != CandleStates.Finished)
return;
if (!_hasPrev)
{
_prevMacd = macd;
_hasPrev = true;
return;
}
var price = candle.ClosePrice;
_barsFromTrade++;
var canTrade = _barsFromTrade >= CooldownBars;
var crossUp = _prevMacd <= 0 && macd > 0;
var crossDown = _prevMacd >= 0 && macd < 0;
if (Position == 0 && canTrade)
{
if (crossUp)
{
BuyMarket();
_entryPrice = price;
_barsFromTrade = 0;
}
else if (crossDown)
{
SellMarket();
_entryPrice = price;
_barsFromTrade = 0;
}
}
else if (Position > 0)
{
var tp = _entryPrice * (1m + TakeProfitPercent / 100m);
var sl = _entryPrice * (1m - StopLossPercent / 100m);
if (price >= tp || price <= sl || (canTrade && crossDown))
{
SellMarket();
_entryPrice = 0;
_barsFromTrade = 0;
}
}
else if (Position < 0)
{
var tp = _entryPrice * (1m - TakeProfitPercent / 100m);
var sl = _entryPrice * (1m + StopLossPercent / 100m);
if (price <= tp || price >= sl || (canTrade && crossUp))
{
BuyMarket();
_entryPrice = 0;
_barsFromTrade = 0;
}
}
_prevMacd = macd;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class martingale_with_macd_kdj_opening_conditions_strategy(Strategy):
"""
Martingale with MACD zero-line crossover entry and percent-based SL/TP.
"""
def __init__(self):
super(martingale_with_macd_kdj_opening_conditions_strategy, self).__init__()
self._take_profit_pct = self.Param("TakeProfitPercent", 4.0).SetDisplay("TP %", "Take profit percent", "Risk")
self._stop_loss_pct = self.Param("StopLossPercent", 8.0).SetDisplay("SL %", "Stop loss percent", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 30).SetDisplay("Cooldown", "Bars between trades", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Candles", "General")
self._prev_macd = 0.0
self._has_prev = False
self._entry_price = 0.0
self._bars_from_trade = 30
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(martingale_with_macd_kdj_opening_conditions_strategy, self).OnReseted()
self._prev_macd = 0.0
self._has_prev = False
self._entry_price = 0.0
self._bars_from_trade = self._cooldown_bars.Value
def OnStarted2(self, time):
super(martingale_with_macd_kdj_opening_conditions_strategy, self).OnStarted2(time)
self._prev_macd = 0.0
self._has_prev = False
self._entry_price = 0.0
self._bars_from_trade = self._cooldown_bars.Value
macd = MovingAverageConvergenceDivergence()
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 = float(macd_val)
if not self._has_prev:
self._prev_macd = macd
self._has_prev = True
return
close = float(candle.ClosePrice)
self._bars_from_trade += 1
can_trade = self._bars_from_trade >= self._cooldown_bars.Value
cross_up = self._prev_macd <= 0 and macd > 0
cross_down = self._prev_macd >= 0 and macd < 0
tp_pct = float(self._take_profit_pct.Value) / 100.0
sl_pct = float(self._stop_loss_pct.Value) / 100.0
if self.Position == 0 and can_trade:
if cross_up:
self.BuyMarket()
self._entry_price = close
self._bars_from_trade = 0
elif cross_down:
self.SellMarket()
self._entry_price = close
self._bars_from_trade = 0
elif self.Position > 0:
tp = self._entry_price * (1.0 + tp_pct)
sl = self._entry_price * (1.0 - sl_pct)
if close >= tp or close <= sl or (can_trade and cross_down):
self.SellMarket()
self._entry_price = 0.0
self._bars_from_trade = 0
elif self.Position < 0:
tp = self._entry_price * (1.0 - tp_pct)
sl = self._entry_price * (1.0 + sl_pct)
if close <= tp or close >= sl or (can_trade and cross_up):
self.BuyMarket()
self._entry_price = 0.0
self._bars_from_trade = 0
self._prev_macd = macd
def CreateClone(self):
return martingale_with_macd_kdj_opening_conditions_strategy()