在 GitHub 上查看

Exp Price Position

Exp Price Position 策略来自原始的 MetaTrader 专家顾问,它结合价格位置与趋势过滤。 策略通过两条中值移动平均线确定最近的摆动水平,然后使用一对平滑移动平均线来识别趋势方向。 只有当价格位置与趋势方向一致并且与当前K线结构相符时才会开仓。

该策略适用于在价格回撤到动态中值水平后趋势反转的市场。通过移动止损和收益比来管理风险。

细节

  • 入场条件:价格在最后摆动水平之上且趋势向上时做多;在其下方且趋势向下时做空。
  • 做多/做空:双向。
  • 出场条件:反向信号或保护性止损。
  • 止损:有,采用移动止损和盈亏比。
  • 默认值
    • FastPeriod = 2
    • SlowPeriod = 30
    • MedianFastPeriod = 26
    • MedianSlowPeriod = 20
    • TpSlRatio = 3m
    • TrailingStopPips = 10m
    • CandleType = TimeSpan.FromHours(1)
  • 过滤器
    • 类别:趋势跟随
    • 方向:双向
    • 指标:Smoothed Moving Average, Simple Moving Average
    • 止损:移动止损
    • 复杂度:中等
    • 时间框架:日内
    • 季节性:否
    • 神经网络:否
    • 背离:否
    • 风险水平:中等
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;
	}
}