Exp Price Position
The Exp Price Position strategy adapts the original MetaTrader expert advisor that combines price location and a step trend filter. It evaluates the relationship between two median moving averages to locate the most recent swing level and then checks a fast and slow smoothed moving average pair to determine trend direction. Orders are opened only when both the price position and step trend agree with the current candle structure.
The strategy is designed for markets where trend shifts occur after price pulls back to a dynamic median level. A trailing stop and take-profit ratio are applied to manage risk.
Details
- Entry Criteria: Price above last swing level with bullish step trend for long trades; below with bearish step trend for short trades.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal or protective stop.
- Stops: Yes, via trailing stop with take-profit ratio.
- Default Values:
FastPeriod= 2SlowPeriod= 30MedianFastPeriod= 26MedianSlowPeriod= 20TpSlRatio= 3mTrailingStopPips= 10mCandleType= TimeSpan.FromHours(1)
- Filters:
- Category: Trend Following
- Direction: Both
- Indicators: Smoothed Moving Average, Simple Moving Average
- Stops: Trailing
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Port of the ExpPricePosition MetaTrader expert.
/// Combines price position with a step trend filter based on smoothed moving averages.
/// </summary>
public class ExpPricePositionStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _medianFastPeriod;
private readonly StrategyParam<int> _medianSlowPeriod;
private readonly StrategyParam<decimal> _tpSlRatio;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<bool> _useTrailingStop;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevFast;
private decimal? _prevSlow;
private decimal _lastCrossLevel;
private bool _hasCrossLevel;
/// <summary>
/// Fast smoothed moving average period (default: 2).
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow smoothed moving average period (default: 30).
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Median SMMA period used for price position (default: 26).
/// </summary>
public int MedianFastPeriod
{
get => _medianFastPeriod.Value;
set => _medianFastPeriod.Value = value;
}
/// <summary>
/// Median SMA period used for price position (default: 20).
/// </summary>
public int MedianSlowPeriod
{
get => _medianSlowPeriod.Value;
set => _medianSlowPeriod.Value = value;
}
/// <summary>
/// Take profit to stop loss ratio (default: 3).
/// </summary>
public decimal TpSlRatio
{
get => _tpSlRatio.Value;
set => _tpSlRatio.Value = value;
}
/// <summary>
/// Trailing stop value in points (default: 10).
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Use trailing stop protection.
/// </summary>
public bool UseTrailingStop
{
get => _useTrailingStop.Value;
set => _useTrailingStop.Value = value;
}
/// <summary>
/// Candle type for subscription.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="ExpPricePositionStrategy"/>.
/// </summary>
public ExpPricePositionStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 2)
.SetDisplay("Fast Period", "Fast SMMA period", "Parameters")
.SetGreaterThanZero();
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetDisplay("Slow Period", "Slow SMMA period", "Parameters")
.SetGreaterThanZero();
_medianFastPeriod = Param(nameof(MedianFastPeriod), 26)
.SetDisplay("Median Fast Period", "Median SMMA period", "Parameters")
.SetGreaterThanZero();
_medianSlowPeriod = Param(nameof(MedianSlowPeriod), 20)
.SetDisplay("Median Slow Period", "Median SMA period", "Parameters")
.SetGreaterThanZero();
_tpSlRatio = Param(nameof(TpSlRatio), 3m)
.SetDisplay("TP/SL Ratio", "Take profit to stop loss ratio", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 10m)
.SetDisplay("Trailing Stop", "Trailing stop value in points", "Risk");
_useTrailingStop = Param(nameof(UseTrailingStop), true)
.SetDisplay("Use Trailing Stop", "Enable trailing stop", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
_lastCrossLevel = 0m;
_hasCrossLevel = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (UseTrailingStop)
{
StartProtection(
stopLoss: new Unit(TrailingStopPips, UnitTypes.Absolute),
takeProfit: new Unit(TrailingStopPips * TpSlRatio, UnitTypes.Absolute));
}
var fast = new SmoothedMovingAverage { Length = FastPeriod };
var slow = new SmoothedMovingAverage { Length = SlowPeriod };
var medianFast = new SmoothedMovingAverage { Length = MedianFastPeriod };
var medianSlow = new SMA { Length = MedianSlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, medianFast, medianSlow, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle,
decimal fast, decimal slow, decimal medianFast, decimal medianSlow)
{
if (candle.State != CandleStates.Finished)
return;
var signal = (medianFast + medianSlow) / 2m;
if (candle.OpenPrice <= signal && candle.ClosePrice > signal)
{
_lastCrossLevel = candle.LowPrice;
_hasCrossLevel = true;
}
else if (candle.OpenPrice >= signal && candle.ClosePrice < signal)
{
_lastCrossLevel = candle.HighPrice;
_hasCrossLevel = true;
}
if (!_hasCrossLevel)
{
_prevFast = fast;
_prevSlow = slow;
return;
}
if (_prevFast is null || _prevSlow is null)
{
_prevFast = fast;
_prevSlow = slow;
return;
}
var pricePos = candle.ClosePrice > _lastCrossLevel ? 1 : -1;
var stepUp = fast > slow && fast > _prevFast && _prevFast > _prevSlow;
var stepDown = fast < slow && fast < _prevFast && _prevFast < _prevSlow;
if (pricePos > 0 && stepUp && candle.ClosePrice > candle.OpenPrice && candle.LowPrice < fast && Position <= 0)
BuyMarket();
else if (pricePos < 0 && stepDown && candle.ClosePrice < candle.OpenPrice && candle.HighPrice > fast && Position >= 0)
SellMarket();
_prevFast = fast;
_prevSlow = slow;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import SmoothedMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_price_position_strategy(Strategy):
"""
Price position with step trend filter based on smoothed moving averages.
Combines SMMA crossover with price position relative to signal level.
"""
def __init__(self):
super(exp_price_position_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 2) \
.SetDisplay("Fast Period", "Fast SMMA period", "Parameters")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow Period", "Slow SMMA period", "Parameters")
self._median_fast_period = self.Param("MedianFastPeriod", 26) \
.SetDisplay("Median Fast Period", "Median SMMA period", "Parameters")
self._median_slow_period = self.Param("MedianSlowPeriod", 20) \
.SetDisplay("Median Slow Period", "Median SMA period", "Parameters")
self._tp_sl_ratio = self.Param("TpSlRatio", 3.0) \
.SetDisplay("TP/SL Ratio", "Take profit to stop loss ratio", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 10.0) \
.SetDisplay("Trailing Stop", "Trailing stop in points", "Risk")
self._use_trailing = self.Param("UseTrailingStop", True) \
.SetDisplay("Use Trailing Stop", "Enable trailing stop", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._prev_fast = None
self._prev_slow = None
self._last_cross_level = 0.0
self._has_cross_level = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_price_position_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
self._last_cross_level = 0.0
self._has_cross_level = False
def OnStarted2(self, time):
super(exp_price_position_strategy, self).OnStarted2(time)
if self._use_trailing.Value:
ts = float(self._trailing_stop_pips.Value)
ratio = float(self._tp_sl_ratio.Value)
self.StartProtection(
Unit(float(ts * ratio), UnitTypes.Absolute),
Unit(float(ts), UnitTypes.Absolute)
)
fast = SmoothedMovingAverage()
fast.Length = self._fast_period.Value
slow = SmoothedMovingAverage()
slow.Length = self._slow_period.Value
median_fast = SmoothedMovingAverage()
median_fast.Length = self._median_fast_period.Value
median_slow = SimpleMovingAverage()
median_slow.Length = self._median_slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, median_fast, median_slow, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val, mf_val, ms_val):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_val)
slow_val = float(slow_val)
mf_val = float(mf_val)
ms_val = float(ms_val)
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
signal = (mf_val + ms_val) / 2.0
if open_p <= signal and close > signal:
self._last_cross_level = float(candle.LowPrice)
self._has_cross_level = True
elif open_p >= signal and close < signal:
self._last_cross_level = float(candle.HighPrice)
self._has_cross_level = True
if not self._has_cross_level:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
price_pos = 1 if close > self._last_cross_level else -1
step_up = fast_val > slow_val and fast_val > self._prev_fast and self._prev_fast > self._prev_slow
step_down = fast_val < slow_val and fast_val < self._prev_fast and self._prev_fast < self._prev_slow
if (price_pos > 0 and step_up and close > open_p and
float(candle.LowPrice) < fast_val and self.Position <= 0):
self.BuyMarket()
elif (price_pos < 0 and step_down and close < open_p and
float(candle.HighPrice) > fast_val and self.Position >= 0):
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return exp_price_position_strategy()