MA Crossover Demand Supply Zones SLTP Strategy
This strategy combines a short/long simple moving average crossover with demand and supply zone detection. The system looks for crossovers occurring near recently confirmed demand or supply zones, then enters in the direction of the crossover and manages the position with fixed-percent stop loss and take profit.
Details
- Entry Criteria:
- Long: short SMA crosses above long SMA near a demand zone.
- Short: short SMA crosses below long SMA near a supply zone.
- Long/Short: Both sides.
- Exit Criteria:
- Price hits take profit or stop loss levels.
- Stops: Percent-based stop loss and take profit.
- Default Values:
ShortMaLength= 9LongMaLength= 21ZoneLookback= 50ZoneStrength= 2StopLossPercent= 1TakeProfitPercent= 2
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: SMA, Highest, Lowest
- Stops: Yes
- Complexity: Basic
- Timeframe: Any
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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>
/// MA crossover with demand and supply zones and percent stop loss / take profit.
/// The demand zone is the lowest low and the supply zone the highest high of the last ZoneLookback candles. Price is near a zone
/// when the close is within ZoneStrength percent of it. A short SMA crossing above the long SMA near the demand zone goes long, a cross
/// below near the supply zone goes short, reversing an opposite position. Positions exit on percent stop loss and take profit.
/// </summary>
public class MaCrossoverDemandSupplyZonesSltpStrategy : Strategy
{
private readonly StrategyParam<int> _shortMaLength;
private readonly StrategyParam<int> _longMaLength;
private readonly StrategyParam<int> _zoneLookback;
private readonly StrategyParam<decimal> _zoneStrength;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevShort;
private decimal? _prevLong;
/// <summary>
/// Short SMA period.
/// </summary>
public int ShortMaLength
{
get => _shortMaLength.Value;
set => _shortMaLength.Value = value;
}
/// <summary>
/// Long SMA period.
/// </summary>
public int LongMaLength
{
get => _longMaLength.Value;
set => _longMaLength.Value = value;
}
/// <summary>
/// Candles used to find demand and supply zones.
/// </summary>
public int ZoneLookback
{
get => _zoneLookback.Value;
set => _zoneLookback.Value = value;
}
/// <summary>
/// Distance from a zone, in percent, that counts as near.
/// </summary>
public decimal ZoneStrength
{
get => _zoneStrength.Value;
set => _zoneStrength.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Take profit percentage.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MaCrossoverDemandSupplyZonesSltpStrategy()
{
_shortMaLength = Param(nameof(ShortMaLength), 9)
.SetGreaterThanZero()
.SetDisplay("Short MA", "Short SMA period", "Indicators");
_longMaLength = Param(nameof(LongMaLength), 21)
.SetGreaterThanZero()
.SetDisplay("Long MA", "Long SMA period", "Indicators");
_zoneLookback = Param(nameof(ZoneLookback), 50)
.SetGreaterThanZero()
.SetDisplay("Zone Lookback", "Candles used to find demand and supply zones", "Zones");
_zoneStrength = Param(nameof(ZoneStrength), 2m)
.SetNotNegative()
.SetDisplay("Zone Strength", "Distance from a zone in percent that counts as near", "Zones");
_stopLossPercent = Param(nameof(StopLossPercent), 1m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take profit percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevShort = null;
_prevLong = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevShort = null;
_prevLong = null;
var shortMa = new SimpleMovingAverage { Length = ShortMaLength };
var longMa = new SimpleMovingAverage { Length = LongMaLength };
var highest = new Highest { Length = ZoneLookback };
var lowest = new Lowest { Length = ZoneLookback };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(shortMa, longMa, highest, lowest, ProcessCandle)
.Start();
StartProtection(
TakeProfitPercent > 0 ? new Unit(TakeProfitPercent, UnitTypes.Percent) : new Unit(),
StopLossPercent > 0 ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit(),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, shortMa);
DrawIndicator(area, longMa);
DrawIndicator(area, highest);
DrawIndicator(area, lowest);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue shortValue, IIndicatorValue longValue, IIndicatorValue highestValue, IIndicatorValue lowestValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!shortValue.IsFormed || !longValue.IsFormed || !highestValue.IsFormed || !lowestValue.IsFormed)
return;
var shortMa = shortValue.GetValue<decimal>();
var longMa = longValue.GetValue<decimal>();
var supply = highestValue.GetValue<decimal>();
var demand = lowestValue.GetValue<decimal>();
var prevShort = _prevShort;
var prevLong = _prevLong;
_prevShort = shortMa;
_prevLong = longMa;
if (prevShort is not decimal ps || prevLong is not decimal pl)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var nearDemand = close <= demand * (1m + ZoneStrength / 100m);
var nearSupply = close >= supply * (1m - ZoneStrength / 100m);
var crossUp = ps <= pl && shortMa > longMa;
var crossDown = ps >= pl && shortMa < longMa;
if (crossUp && nearDemand && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (crossDown && nearSupply && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class ma_crossover_demand_supply_zones_sltp_strategy(Strategy):
"""
MA crossover with demand and supply zones and percent stop loss / take profit.
The demand zone is the lowest low and the supply zone the highest high of the last ZoneLookback candles. Price is near a zone
when the close is within ZoneStrength percent of it. A short SMA crossing above the long SMA near the demand zone goes long, a cross
below near the supply zone goes short, reversing an opposite position. Positions exit on percent stop loss and take profit.
"""
def __init__(self):
super(ma_crossover_demand_supply_zones_sltp_strategy, self).__init__()
self._short_ma_length = self.Param("ShortMaLength", 9).SetGreaterThanZero().SetDisplay("Short MA", "Short SMA period", "Indicators")
self._long_ma_length = self.Param("LongMaLength", 21).SetGreaterThanZero().SetDisplay("Long MA", "Long SMA period", "Indicators")
self._zone_lookback = self.Param("ZoneLookback", 50).SetGreaterThanZero().SetDisplay("Zone Lookback", "Candles used to find demand and supply zones", "Zones")
self._zone_strength = self.Param("ZoneStrength", 2.0).SetNotNegative().SetDisplay("Zone Strength", "Distance from a zone in percent that counts as near", "Zones")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 2.0).SetNotNegative().SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_short = None
self._prev_long = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_crossover_demand_supply_zones_sltp_strategy, self).OnReseted()
self._prev_short = None
self._prev_long = None
def OnStarted2(self, time):
super(ma_crossover_demand_supply_zones_sltp_strategy, self).OnStarted2(time)
self._prev_short = None
self._prev_long = None
short_ma = SimpleMovingAverage()
short_ma.Length = self._short_ma_length.Value
long_ma = SimpleMovingAverage()
long_ma.Length = self._long_ma_length.Value
highest = Highest()
highest.Length = self._zone_lookback.Value
lowest = Lowest()
lowest.Length = self._zone_lookback.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(short_ma, long_ma, highest, lowest, self._process_candle).Start()
tp = self._take_profit_percent.Value
sl = self._stop_loss_percent.Value
self.StartProtection(
Unit(Decimal(tp), UnitTypes.Percent) if tp > 0 else Unit(),
Unit(Decimal(sl), UnitTypes.Percent) if sl > 0 else Unit(),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, short_ma)
self.DrawIndicator(area, long_ma)
self.DrawIndicator(area, highest)
self.DrawIndicator(area, lowest)
self.DrawOwnTrades(area)
def _process_candle(self, candle, short_value, long_value, highest_value, lowest_value):
if candle.State != CandleStates.Finished:
return
if not short_value.IsFormed or not long_value.IsFormed or not highest_value.IsFormed or not lowest_value.IsFormed:
return
short_ma = short_value.GetValue[Decimal](None)
long_ma = long_value.GetValue[Decimal](None)
supply = highest_value.GetValue[Decimal](None)
demand = lowest_value.GetValue[Decimal](None)
prev_short = self._prev_short
prev_long = self._prev_long
self._prev_short = short_ma
self._prev_long = long_ma
if prev_short is None or prev_long is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
strength = Decimal(self._zone_strength.Value) / Decimal(100)
near_demand = close <= demand * (Decimal(1) + strength)
near_supply = close >= supply * (Decimal(1) - strength)
cross_up = prev_short <= prev_long and short_ma > long_ma
cross_down = prev_short >= prev_long and short_ma < long_ma
if cross_up and near_demand and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif cross_down and near_supply and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return ma_crossover_demand_supply_zones_sltp_strategy()