在 GitHub 上查看

Ma Shift Puria Method 策略

概述

Ma Shift Puria Method 策略是经典 Puria 专家顾问在 StockSharp 高阶 API 上的复刻。算法结合多条指数移动平均线(EMA)、MACD 过滤器以及可选的分形追踪逻辑,仅在完全收盘的 K 线后进行判断。仓位管理包含固定止损/止盈、可配置的追踪止损,以及当价格接近目标时基于分形的保护机制。

指标与计算

  • 快 EMA(默认 14):衡量短期动量,用于判断快速均线的斜率。
  • 慢 EMA(默认 80):反映大趋势。快慢 EMA 之间的距离必须超过用户定义的点数阈值才能生成信号。
  • MACD(快 11、慢 102、信号线 9):要求主线在最新柱上穿越零轴,同时三根 K 线之前位于相反的一侧,以确认动量方向。
  • 分形窗口(5 根 K 线):启用分形追踪时使用。策略在滚动的五根 K 线窗口中提取摆动高点和低点,与 MetaTrader 的分形定义(中心柱为局部极值)一致。

入场条件

策略仅在允许交易且最新收盘柱满足以下条件时开仓:

多头

  1. 快 EMA 在慢 EMA 之上。
  2. 慢 EMA 相比三根 K 线前抬升。
  3. 快 EMA 呈上升斜率(当前值高于前一根)。
  4. MACD 主线大于零,且三根 K 线前在零轴以下。
  5. 快 EMA 在最近两根 K 线间的增幅超过 Shift Minimum 参数(点数),并且要么仍在加速,要么上一段增幅小于等于零。

空头

  1. 快 EMA 在慢 EMA 之下。
  2. 慢 EMA 相比三根 K 线前下降。
  3. 快 EMA 呈下降斜率(当前值低于前一根)。
  4. MACD 主线小于零,且三根 K 线前在零轴以上。
  5. 快 EMA 在最近两根 K 线间的降幅超过 Shift Minimum 阈值,并且要么继续加速,要么上一段降幅大于等于零。

根据配置,策略可以按固定手数开仓,或根据账户风险动态计算下单数量。当存在反向仓位时,会一次性平掉并按当前方向重新建仓。

出场与风险管理

  • 止损:以点数为单位,相对于入场价格设置。若蜡烛的最低/最高触及该水平,则立即平仓。
  • 止盈:同样以点数表示,触发后全部离场。
  • 追踪止损:开启后,当浮动盈亏超过“追踪距离 + 追踪步长”时,止损会按配置距离跟随价格移动,仅在能够前进至少一个追踪步长时更新,行为与原 MQL 版本一致。
  • 分形追踪:可选功能。当价格达到止盈距离的 95% 时,可把止损上调至最近的分形低点(多头)或下调至分形高点(空头),在接近目标时锁定利润。
  • 风险控制下单:关闭手数固定时,策略按账户权益的一定百分比承担风险。风险金额除以货币化后的止损距离,并根据交易所的最小变动手数/上下限进行四舍五入。

参数

参数 说明 默认值
UseManualVolume 是否使用固定手数。 true
ManualVolume 固定手数模式下的下单数量。 0.1
RiskPercent 风险百分比(当 UseManualVolume=false 时生效)。 9
StopLossPips 止损点数。 45
TakeProfitPips 止盈点数。 75
TrailingStopPips 追踪止损距离。 15
TrailingStepPips 更新追踪止损所需的最小点数增量。 5
MaxPositions 同方向最多累积的单位数量。 1
ShiftMinPips 判定信号所需的最小 EMA 斜率(点数)。 20
FastLength 快 EMA 长度。 14
SlowLength 慢 EMA 长度。 80
MacdFast MACD 快线周期。 11
MacdSlow MACD 慢线周期。 102
UseFractalTrailing 是否启用分形追踪止损。 false
CandleType 使用的 K 线类型/周期。 15 分钟

实现细节

  • 通过 SubscribeCandles().Bind(...) 绑定 EMA 与 MACD,避免手动读取指标缓存,完全符合高阶 API 使用方式。
  • 使用内部状态保存最近三根 EMA 与 MACD 数值,以模拟原始 MQL 代码中的 shift 访问。
  • 分形逻辑基于 5 根窗口手工计算,不调用指标的 GetValue 方法,严格遵守仓库规范。
  • 止损、止盈以及追踪调整通过市价单实现,与原 EA 修改持仓的效果一致。
  • StartProtection() 在启动时调用,用于断线保护和持仓状态跟踪。

使用建议

  1. 选择与原 Puria 策略相符的时间框架(例如 15 分钟外汇图)。
  2. 根据品种的点值调节参数,算法会自动处理 5 位报价,但特殊品种仍需手动确认。
  3. 启用风险下单前,请确认账户权益及交易所最小手数限制,确保计算出的数量可成交。
  4. 可结合交易时段过滤、组合风控等模块使用,本策略主要复现原 EA 的信号与持仓管理逻辑。
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>
/// Puria method strategy that monitors EMA slopes and MACD momentum with optional trailing management.
/// </summary>
public class MaShiftPuriaMethodStrategy : Strategy
{
	private readonly StrategyParam<bool> _useManualVolume;
	private readonly StrategyParam<decimal> _manualVolume;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<int> _maxPositions;
	private readonly StrategyParam<decimal> _shiftMinPips;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _macdFast;
	private readonly StrategyParam<int> _macdSlow;
	private readonly StrategyParam<bool> _useFractalTrailing;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _fastEma = null!;
	private ExponentialMovingAverage _slowEma = null!;
	private MovingAverageConvergenceDivergence _macd = null!;

	private decimal? _fastPrev1;
	private decimal? _fastPrev2;
	private decimal? _fastPrev3;
	private decimal? _slowPrev1;
	private decimal? _slowPrev2;
	private decimal? _slowPrev3;
	private decimal? _macdPrev1;
	private decimal? _macdPrev2;
	private decimal? _macdPrev3;

	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;
	private decimal? _longTakePrice;
	private decimal? _shortTakePrice;

	private readonly decimal[] _highWindow = new decimal[5];
	private readonly decimal[] _lowWindow = new decimal[5];
	private int _fractalCount;
	private decimal? _lastUpperFractal;
	private decimal? _lastLowerFractal;

	/// <summary>
	/// Use manual volume instead of risk-based sizing.
	/// </summary>
	public bool UseManualVolume
	{
		get => _useManualVolume.Value;
		set => _useManualVolume.Value = value;
	}

	/// <summary>
	/// Manual trade volume.
	/// </summary>
	public decimal ManualVolume
	{
		get => _manualVolume.Value;
		set => _manualVolume.Value = value;
	}

	/// <summary>
	/// Risk percentage used when calculating position size.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public int TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Minimum move required before updating the trailing stop.
	/// </summary>
	public int TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Maximum number of position units allowed in one direction.
	/// </summary>
	public int MaxPositions
	{
		get => _maxPositions.Value;
		set => _maxPositions.Value = value;
	}

	/// <summary>
	/// Minimum EMA separation (in pips) required for a valid signal.
	/// </summary>
	public decimal ShiftMinPips
	{
		get => _shiftMinPips.Value;
		set => _shiftMinPips.Value = value;
	}

	/// <summary>
	/// Fast EMA length.
	/// </summary>
	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length.
	/// </summary>
	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	/// <summary>
	/// MACD fast period.
	/// </summary>
	public int MacdFast
	{
		get => _macdFast.Value;
		set => _macdFast.Value = value;
	}

	/// <summary>
	/// MACD slow period.
	/// </summary>
	public int MacdSlow
	{
		get => _macdSlow.Value;
		set => _macdSlow.Value = value;
	}

	/// <summary>
	/// Enable fractal-based trailing stop adjustments.
	/// </summary>
	public bool UseFractalTrailing
	{
		get => _useFractalTrailing.Value;
		set => _useFractalTrailing.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public MaShiftPuriaMethodStrategy()
	{
		_useManualVolume = Param(nameof(UseManualVolume), true)
		.SetDisplay("Manual Volume", "Use fixed trade volume", "Risk");

		_manualVolume = Param(nameof(ManualVolume), 0.1m)
		.SetGreaterThanZero()
		.SetDisplay("Volume", "Fixed trade volume", "Risk");

		_riskPercent = Param(nameof(RiskPercent), 9m)
		.SetGreaterThanZero()
		.SetDisplay("Risk %", "Portfolio risk percent per trade", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 45)
		.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 75)
		.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 15)
		.SetDisplay("Trailing Stop (pips)", "Trailing stop distance", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5)
		.SetDisplay("Trailing Step (pips)", "Minimum advance before trailing", "Risk");

		_maxPositions = Param(nameof(MaxPositions), 1)
		.SetGreaterThanZero()
		.SetDisplay("Max Positions", "Maximum units per direction", "Risk");

		_shiftMinPips = Param(nameof(ShiftMinPips), 20m)
		.SetGreaterThanZero()
		.SetDisplay("Shift Minimum", "Minimal EMA separation in pips", "Signals");

		_fastLength = Param(nameof(FastLength), 14)
		.SetGreaterThanZero()
		.SetDisplay("Fast EMA", "Fast EMA length", "Indicators");

		_slowLength = Param(nameof(SlowLength), 80)
		.SetGreaterThanZero()
		.SetDisplay("Slow EMA", "Slow EMA length", "Indicators");

		_macdFast = Param(nameof(MacdFast), 11)
		.SetGreaterThanZero()
		.SetDisplay("MACD Fast", "MACD fast period", "Indicators");

		_macdSlow = Param(nameof(MacdSlow), 102)
		.SetGreaterThanZero()
		.SetDisplay("MACD Slow", "MACD slow period", "Indicators");

		_useFractalTrailing = Param(nameof(UseFractalTrailing), false)
		.SetDisplay("Fractal Trailing", "Enable fractal trailing stop", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
		.SetDisplay("Candle Type", "Candles used for calculations", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_fastPrev1 = null;
		_fastPrev2 = null;
		_fastPrev3 = null;
		_slowPrev1 = null;
		_slowPrev2 = null;
		_slowPrev3 = null;
		_macdPrev1 = null;
		_macdPrev2 = null;
		_macdPrev3 = null;

		ResetLongState();
		ResetShortState();

		Array.Clear(_highWindow, 0, _highWindow.Length);
		Array.Clear(_lowWindow, 0, _lowWindow.Length);
		_fractalCount = 0;
		_lastUpperFractal = null;
		_lastLowerFractal = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_fastEma = new ExponentialMovingAverage { Length = FastLength };
		_slowEma = new ExponentialMovingAverage { Length = SlowLength };
		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = MacdFast },
			LongMa = { Length = MacdSlow },
		};

		Volume = ManualVolume;

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_fastEma, _slowEma, _macd, ProcessCandle)
		.Start();

		var priceArea = CreateChartArea();
		if (priceArea != null)
		{
			DrawCandles(priceArea, subscription);
			DrawIndicator(priceArea, _fastEma);
			DrawIndicator(priceArea, _slowEma);

			var macdArea = CreateChartArea();
			if (macdArea != null)
			{
				macdArea.Title = "MACD";
				DrawIndicator(macdArea, _macd);
			}

			DrawOwnTrades(priceArea);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal macdMain)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var pip = GetPipSize();

		UpdateFractals(candle);

		var prevFast1 = _fastPrev1;
		var prevFast2 = _fastPrev2;
		var prevFast3 = _fastPrev3;
		var prevSlow1 = _slowPrev1;
		var prevSlow3 = _slowPrev3;
		var prevMacd1 = _macdPrev1;
		var prevMacd3 = _macdPrev3;

		UpdateHistory(fast, slow, macdMain);

		ManageLongPosition(candle, pip);
		ManageShortPosition(candle, pip);

		if (!prevFast1.HasValue || !prevFast2.HasValue || !prevFast3.HasValue ||
		!prevSlow1.HasValue || !prevSlow3.HasValue || !prevMacd1.HasValue || !prevMacd3.HasValue)
		{
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var fast1 = prevFast1.Value;
		var fast2 = prevFast2.Value;
		var fast3 = prevFast3.Value;
		var slow1 = prevSlow1.Value;
		var slow3 = prevSlow3.Value;
		var macd1 = prevMacd1.Value;
		var macd3 = prevMacd3.Value;

		if (pip <= 0m)
			pip = 0.0001m;

		var x1Long = (fast1 - fast2) / pip;
		var x2Long = (fast2 - fast3) / pip;

		var x1Short = (fast2 - fast1) / pip;
		var x2Short = (fast3 - fast2) / pip;

		var shiftRequirement = ShiftMinPips;

		var buySignal = fast1 > slow1 &&
		slow1 > slow3 &&
		fast1 > fast2 &&
		macd1 > 0m &&
		macd3 < 0m &&
		x1Long > shiftRequirement &&
		(x1Long >= x2Long || x2Long <= 0m);

		var sellSignal = fast1 < slow1 &&
		slow1 < slow3 &&
		fast1 < fast2 &&
		macd1 < 0m &&
		macd3 > 0m &&
		x1Short > shiftRequirement &&
		(x1Short >= x2Short || x2Short <= 0m);

		if (buySignal)
		{
			TryEnterLong(candle, pip);
		}
		else if (sellSignal)
		{
			TryEnterShort(candle, pip);
		}
	}

	private void TryEnterLong(ICandleMessage candle, decimal pip)
	{
		var stopDistance = StopLossPips > 0 ? StopLossPips * pip : 0m;
		var volumePerTrade = GetTradeVolume(stopDistance);
		if (volumePerTrade <= 0m)
			return;

		var maxVolume = volumePerTrade * MaxPositions;
		if (maxVolume <= 0m)
			return;

		var limit = maxVolume - Position;
		if (limit <= 0m)
			return;

		var volumeToBuy = volumePerTrade;
		if (Position < 0m)
			volumeToBuy += -Position;

		if (volumeToBuy > limit)
			volumeToBuy = limit;

		if (volumeToBuy <= 0m)
			return;

		BuyMarket(volumeToBuy);

		_longEntryPrice = candle.ClosePrice;
		_longStopPrice = StopLossPips > 0 ? candle.ClosePrice - stopDistance : null;
		_longTakePrice = TakeProfitPips > 0 ? candle.ClosePrice + TakeProfitPips * pip : null;

		ResetShortState();
	}

	private void TryEnterShort(ICandleMessage candle, decimal pip)
	{
		var stopDistance = StopLossPips > 0 ? StopLossPips * pip : 0m;
		var volumePerTrade = GetTradeVolume(stopDistance);
		if (volumePerTrade <= 0m)
			return;

		var maxVolume = volumePerTrade * MaxPositions;
		if (maxVolume <= 0m)
			return;

		var limit = maxVolume + Position;
		if (limit <= 0m)
			return;

		var volumeToSell = volumePerTrade;
		if (Position > 0m)
			volumeToSell += Position;

		if (volumeToSell > limit)
			volumeToSell = limit;

		if (volumeToSell <= 0m)
			return;

		SellMarket(volumeToSell);

		_shortEntryPrice = candle.ClosePrice;
		_shortStopPrice = StopLossPips > 0 ? candle.ClosePrice + stopDistance : null;
		_shortTakePrice = TakeProfitPips > 0 ? candle.ClosePrice - TakeProfitPips * pip : null;

		ResetLongState();
	}

	private void ManageLongPosition(ICandleMessage candle, decimal pip)
	{
		if (Position <= 0m)
		{
			ResetLongState();
			return;
		}

		if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
		{
			SellMarket(Position);
			ResetLongState();
			return;
		}

		if (_longTakePrice is decimal take && candle.HighPrice >= take)
		{
			SellMarket(Position);
			ResetLongState();
			return;
		}

		if (TrailingStopPips > 0 && _longEntryPrice is decimal entry)
		{
			var distance = TrailingStopPips * pip;
			var step = TrailingStepPips * pip;
			if (distance > 0m)
			{
				var profit = candle.ClosePrice - entry;
				if (profit > (TrailingStopPips + TrailingStepPips) * pip)
				{
					var threshold = candle.ClosePrice - (distance + step);
					if (!_longStopPrice.HasValue || _longStopPrice.Value < threshold)
					{
						_longStopPrice = candle.ClosePrice - distance;
					}
				}
			}
		}

		if (UseFractalTrailing && _longEntryPrice is decimal longEntry && _longStopPrice.HasValue && TakeProfitPips > 0)
		{
			var target = TakeProfitPips * pip;
			if (target > 0m)
			{
				var profit = candle.ClosePrice - longEntry;
				if (profit >= 0.95m * target && _lastLowerFractal is decimal lower && lower > _longStopPrice.Value)
				{
					_longStopPrice = lower;
				}
			}
		}

		if (_longStopPrice is decimal trailing && candle.LowPrice <= trailing)
		{
			SellMarket(Position);
			ResetLongState();
		}
	}

	private void ManageShortPosition(ICandleMessage candle, decimal pip)
	{
		if (Position >= 0m)
		{
			ResetShortState();
			return;
		}

		var shortVolume = Math.Abs(Position);

		if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
		{
			BuyMarket(shortVolume);
			ResetShortState();
			return;
		}

		if (_shortTakePrice is decimal take && candle.LowPrice <= take)
		{
			BuyMarket(shortVolume);
			ResetShortState();
			return;
		}

		if (TrailingStopPips > 0 && _shortEntryPrice is decimal entry)
		{
			var distance = TrailingStopPips * pip;
			var step = TrailingStepPips * pip;
			if (distance > 0m)
			{
				var profit = entry - candle.ClosePrice;
				if (profit > (TrailingStopPips + TrailingStepPips) * pip)
				{
					var threshold = candle.ClosePrice + (distance + step);
					if (!_shortStopPrice.HasValue || _shortStopPrice.Value > threshold)
					{
						_shortStopPrice = candle.ClosePrice + distance;
					}
				}
			}
		}

		if (UseFractalTrailing && _shortEntryPrice is decimal shortEntry && _shortStopPrice.HasValue && TakeProfitPips > 0)
		{
			var target = TakeProfitPips * pip;
			if (target > 0m)
			{
				var profit = shortEntry - candle.ClosePrice;
				if (profit >= 0.95m * target && _lastUpperFractal is decimal upper && upper < _shortStopPrice.Value)
				{
					_shortStopPrice = upper;
				}
			}
		}

		if (_shortStopPrice is decimal trailing && candle.HighPrice >= trailing)
		{
			BuyMarket(shortVolume);
			ResetShortState();
		}
	}

	private void UpdateHistory(decimal fast, decimal slow, decimal macdMain)
	{
		_fastPrev3 = _fastPrev2;
		_fastPrev2 = _fastPrev1;
		_fastPrev1 = fast;

		_slowPrev3 = _slowPrev2;
		_slowPrev2 = _slowPrev1;
		_slowPrev1 = slow;

		_macdPrev3 = _macdPrev2;
		_macdPrev2 = _macdPrev1;
		_macdPrev1 = macdMain;
	}

	private void UpdateFractals(ICandleMessage candle)
	{
		for (var i = 0; i < _highWindow.Length - 1; i++)
		{
			_highWindow[i] = _highWindow[i + 1];
			_lowWindow[i] = _lowWindow[i + 1];
		}

		_highWindow[^1] = candle.HighPrice;
		_lowWindow[^1] = candle.LowPrice;

		if (_fractalCount < _highWindow.Length)
			_fractalCount++;

		if (_fractalCount < _highWindow.Length)
			return;

		var center = _highWindow.Length / 2;
		var potentialUpper = _highWindow[center];
		var potentialLower = _lowWindow[center];

		var isUpper = true;
		for (var i = 0; i < _highWindow.Length; i++)
		{
			if (i == center)
				continue;

			if (_highWindow[i] >= potentialUpper)
			{
				isUpper = false;
				break;
			}
		}

		if (isUpper)
			_lastUpperFractal = potentialUpper;

		var isLower = true;
		for (var i = 0; i < _lowWindow.Length; i++)
		{
			if (i == center)
				continue;

			if (_lowWindow[i] <= potentialLower)
			{
				isLower = false;
				break;
			}
		}

		if (isLower)
			_lastLowerFractal = potentialLower;
	}

	private decimal GetTradeVolume(decimal stopDistance)
	{
		if (UseManualVolume || stopDistance <= 0m)
			return ManualVolume;

		var portfolioValue = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
		if (portfolioValue <= 0m)
			return ManualVolume;

		var riskAmount = portfolioValue * RiskPercent / 100m;
		if (riskAmount <= 0m)
			return ManualVolume;

		var volume = riskAmount / stopDistance;
		if (volume <= 0m)
			return ManualVolume;

		var step = Security?.VolumeStep ?? 0m;
		if (step > 0m)
		{
			var stepsCount = Math.Floor((double)(volume / step));
			volume = stepsCount <= 0 ? step : (decimal)stepsCount * step;
		}

		var minVolume = Security?.MinVolume ?? 0m;
		if (minVolume > 0m && volume < minVolume)
			volume = minVolume;

		var maxVolume = Security?.MaxVolume ?? 0m;
		if (maxVolume > 0m && volume > maxVolume)
			volume = maxVolume;

		return volume;
	}

	private decimal GetPipSize()
	{
		var step = Security?.PriceStep;
		if (step is null || step <= 0m)
			return 0.0001m;

		if (step < 0.01m)
			return step.Value * 10m;

		return step.Value;
	}

	private void ResetLongState()
	{
		_longEntryPrice = null;
		_longStopPrice = null;
		_longTakePrice = null;
	}

	private void ResetShortState()
	{
		_shortEntryPrice = null;
		_shortStopPrice = null;
		_shortTakePrice = null;
	}
}