MAVA Xonax Strategy
This strategy uses exponential moving averages of open and close prices to detect direction changes. Stop loss and take profit distances are derived from high and low EMAs, ensuring trades have predefined risk and reward levels.
Details
- Entry Criteria:
- Long: EMA of open crosses above EMA of close using the last two completed bars.
- Short: EMA of open crosses below EMA of close using the last two completed bars.
- Long/Short: Both
- Stops: Fixed stop loss and take profit based on EMA ranges.
- Default Values:
EmaPeriod= 6CandleType= TimeSpan.FromMinutes(240).TimeFrame()
- Filters:
- Category: Reversal
- Direction: Both
- Indicators: EMA
- Stops: Yes
- Complexity: Basic
- Timeframe: Long-term
- 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>
/// MAVA Xonax: crossing of the OPEN-price EMA above/below the CLOSE-price EMA
/// on the two most recently completed candles. High/low EMA values set initial risk.
/// </summary>
public class MavaXonaxStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private EMA _emaOpen, _emaClose, _emaHigh, _emaLow;
private decimal _previousOpen, _previousClose;
private bool _havePrevious;
private decimal _longStop, _longTake, _shortStop, _shortTake;
private int _cooldownRemaining;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MavaXonaxStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 6).SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA period", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 1).SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait after entry or exit", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(240).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_emaOpen = _emaClose = _emaHigh = _emaLow = null;
_havePrevious = false;
_previousOpen = _previousClose = 0m;
_longStop = _longTake = _shortStop = _shortTake = 0m;
_cooldownRemaining = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_emaOpen = new EMA { Length = EmaPeriod };
_emaClose = new EMA { Length = EmaPeriod };
_emaHigh = new EMA { Length = EmaPeriod };
_emaLow = new EMA { Length = EmaPeriod };
_havePrevious = false;
_cooldownRemaining = 0;
SubscribeCandles(CandleType).Bind(_emaClose, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal closeEma)
{
if (candle.State != CandleStates.Finished)
return;
var coolingDown = _cooldownRemaining > 0;
if (coolingDown)
_cooldownRemaining--;
var open = _emaOpen.Process(candle.OpenPrice, candle.ServerTime, true);
var high = _emaHigh.Process(candle.HighPrice, candle.ServerTime, true);
var low = _emaLow.Process(candle.LowPrice, candle.ServerTime, true);
if (!open.IsFinal || !high.IsFinal || !low.IsFinal)
return;
var openEma = open.ToDecimal();
var highEma = high.ToDecimal();
var lowEma = low.ToDecimal();
var formed = _emaOpen.IsFormed && _emaClose.IsFormed && _emaHigh.IsFormed && _emaLow.IsFormed;
var closed = false;
// Preliminary candle-close protection: true intrabar execution still needs native replay.
if (Position > 0m && (candle.ClosePrice <= _longStop || candle.ClosePrice >= _longTake))
{
SellMarket(Position); // Exit the entire actual position, never just the default order size.
_cooldownRemaining = SignalCooldownBars;
closed = true;
}
else if (Position < 0m && (candle.ClosePrice >= _shortStop || candle.ClosePrice <= _shortTake))
{
BuyMarket(-Position);
_cooldownRemaining = SignalCooldownBars;
closed = true;
}
if (formed && _havePrevious && !closed && !coolingDown && _cooldownRemaining == 0 && Position == 0m && IsFormedAndOnlineAndAllowTrading())
{
var crossUp = _previousOpen <= _previousClose && openEma > closeEma;
var crossDown = _previousOpen >= _previousClose && openEma < closeEma;
var step = Security.PriceStep is > 0m ? Security.PriceStep.Value : 1m;
if (crossUp || crossDown)
{
var takeDistance = Math.Max(highEma - lowEma, 600m * step);
var stopDistance = crossUp
? Math.Clamp(2m * (openEma - lowEma), step, 400m * step)
: Math.Clamp(2m * (highEma - closeEma), step, 400m * step);
// Candle-close anchor is provisional; actual fill-price anchoring remains unverified.
if (crossUp)
{
_longStop = candle.ClosePrice - stopDistance;
_longTake = candle.ClosePrice + takeDistance;
BuyMarket();
}
else
{
_shortStop = candle.ClosePrice + stopDistance;
_shortTake = candle.ClosePrice - takeDistance;
SellMarket();
}
_cooldownRemaining = SignalCooldownBars;
}
}
// Only completed-bar EMA values become the reference for the next crossing.
_previousOpen = openEma;
_previousClose = closeEma;
_havePrevious = formed;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import process_float
class mava_xonax_strategy(Strategy):
"""Long: open-price EMA crosses ABOVE close-price EMA; short: crosses BELOW."""
def __init__(self):
super(mava_xonax_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 6).SetDisplay("EMA Period", "EMA period", "General")
self._cooldown_bars = self.Param("SignalCooldownBars", 1).SetDisplay(
"Signal Cooldown", "Bars to wait after entry or exit", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(240))).SetDisplay(
"Candle Type", "Candles", "General")
self._ema_close = self._ema_open = self._ema_high = self._ema_low = None
self._previous_open = 0.0
self._previous_close = 0.0
self._have_previous = False
self._long_stop = self._long_take = 0.0
self._short_stop = self._short_take = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(mava_xonax_strategy, self).OnReseted()
self._ema_close = self._ema_open = self._ema_high = self._ema_low = None
self._previous_open = self._previous_close = 0.0
self._have_previous = False
self._long_stop = self._long_take = 0.0
self._short_stop = self._short_take = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(mava_xonax_strategy, self).OnStarted2(time)
self._ema_close = ExponentialMovingAverage()
self._ema_open = ExponentialMovingAverage()
self._ema_high = ExponentialMovingAverage()
self._ema_low = ExponentialMovingAverage()
for indicator in (self._ema_close, self._ema_open, self._ema_high, self._ema_low):
indicator.Length = self._ema_period.Value
self._have_previous = False
self._cooldown_remaining = 0
self.SubscribeCandles(self.candle_type).Bind(self._ema_close, self._process_candle).Start()
def _process_candle(self, candle, close_ema_val):
if candle.State != CandleStates.Finished:
return
cooling_down = self._cooldown_remaining > 0
if cooling_down:
self._cooldown_remaining -= 1
open_value = process_float(self._ema_open, candle.OpenPrice, candle.ServerTime, True)
high_value = process_float(self._ema_high, candle.HighPrice, candle.ServerTime, True)
low_value = process_float(self._ema_low, candle.LowPrice, candle.ServerTime, True)
if not open_value.IsFinal or not high_value.IsFinal or not low_value.IsFinal:
return
open_ema = float(open_value)
high_ema = float(high_value)
low_ema = float(low_value)
close_ema = float(close_ema_val)
close = float(candle.ClosePrice)
formed = all(ind.IsFormed for ind in (self._ema_open, self._ema_close, self._ema_high, self._ema_low))
closed = False
# Provisional candle-close risk exits; actual quote/fill behavior needs native replay.
if self.Position > 0 and (close <= self._long_stop or close >= self._long_take):
self.SellMarket(self.Position)
self._cooldown_remaining = self._cooldown_bars.Value
closed = True
elif self.Position < 0 and (close >= self._short_stop or close <= self._short_take):
self.BuyMarket(-self.Position)
self._cooldown_remaining = self._cooldown_bars.Value
closed = True
if formed and self._have_previous and not closed and not cooling_down and self._cooldown_remaining == 0 and self.Position == 0 and self.IsFormedAndOnlineAndAllowTrading():
cross_up = self._previous_open <= self._previous_close and open_ema > close_ema
cross_down = self._previous_open >= self._previous_close and open_ema < close_ema
if cross_up or cross_down:
raw_step = getattr(self.Security, 'PriceStep', None)
step = float(raw_step) if raw_step is not None and float(raw_step) > 0 else 1.0
take_distance = max(high_ema - low_ema, 600.0 * step)
stop_raw = 2.0 * (open_ema - low_ema) if cross_up else 2.0 * (high_ema - close_ema)
stop_distance = max(step, min(stop_raw, 400.0 * step))
if cross_up:
self._long_stop = close - stop_distance
self._long_take = close + take_distance
self.BuyMarket()
else:
self._short_stop = close + stop_distance
self._short_take = close - take_distance
self.SellMarket()
self._cooldown_remaining = self._cooldown_bars.Value
self._previous_open = open_ema
self._previous_close = close_ema
self._have_previous = formed
def CreateClone(self):
return mava_xonax_strategy()