Открыть на GitHub

Стратегия расчёта объёма позиции

Обзор

Стратегия расчёта объёма позиции повторяет логику оригинального советника MetaTrader, который определяет рекомендуемый объём сделки исходя из уровней стоп-лосса и тейк-профита. При запуске стратегия считывает заданные цены, оценивает текущую рыночную стоимость выбранного инструмента и вычисляет риск-показатели с учётом капитала портфеля.

Стратегия не отправляет заявки. Её задача — вывести в журнал подробную информацию по управлению капиталом и предоставить рассчитанные значения через свойства только для чтения. Благодаря этому её удобно использовать трейдерам, которые выставляют сделки вручную и хотят заранее проверить контроль риска.

Параметры

  • Stop Loss Price — ценовой уровень защитного стоп-лосса для планируемой позиции.
  • Take Profit Price — ценовой уровень целевого тейк-профита.
  • Max Loss % — максимальная доля портфеля, которой трейдер готов рискнуть в одной сделке. Значение умножается на стоимость портфеля, что даёт максимально допустимый убыток в денежном выражении.
  • Is Long Position — направление предполагаемой сделки: true для лонга и false для шорта. От направления зависит, как определяется расстояние до стопа и цели.

Все параметры, кроме Max Loss %, исключены из оптимизации, чтобы сохранить ручной характер ввода как у исходного советника.

Детали расчёта

  1. Стоимость портфеля — используется Portfolio.CurrentValue, при отсутствии берётся Portfolio.BeginValue. Если значение недоступно, расчёт прекращается с предупреждением.
  2. Проверка шага цены — необходимо, чтобы у инструмента были заданы Security.PriceStep и Security.StepPrice. Без этих параметров нельзя перевести расстояние по цене в количество шагов и денежную величину.
  3. Определение текущей цены — в первую очередь берётся цена последней сделки. Если её нет, вычисляется среднее между лучшими ценами Bid/Ask, в крайнем случае используется последняя известная цена.
  4. Расстояние в шагах — стоп и цель пересчитываются в количество шагов цены. Результат округляется вверх (decimal.Ceiling), что соответствует использованию MathCeil в MQL и обеспечивает консервативную оценку.
  5. Денежный риск — максимальный убыток равен PortfolioValue * MaxLoss% / 100.
  6. Рекомендуемый объём — убыток на один шаг равен MaxLoss / StopSteps. Деление на StepPrice даёт объём позиции, при котором риск не превышает заданного порога.
  7. Ожидаемая прибыль — произведение количества шагов до тейк-профита, стоимости шага и рекомендуемого объёма.
  8. Соотношение риск/прибыль — отношение шагов тейк-профита к шагам стоп-лосса, полностью повторяет расчёт по пунктам в оригинале.

Каждый результат сохраняется в свойствах стратегии и выводится в журнал с подробными сообщениями на английском языке. Если соотношение риск/прибыль ≥ 3, выводится фраза «You can trade», иначе публикуется предупреждение о повышенном риске.

Порядок работы

  1. Привяжите стратегию к нужному инструменту и портфелю в среде StockSharp.
  2. Укажите ценовые уровни стоп-лосса и тейк-профита для планируемой сделки.
  3. Задайте допустимый процент риска и направление позиции.
  4. Запустите стратегию — все показатели сразу появятся в журнале.
  5. Перед ручным выставлением ордера изучите рекомендуемый объём и соотношение риск/прибыль.

Дополнительные замечания

  • Если у инструмента отсутствуют значения шага цены или стоимости шага, запросите их у площадки либо задайте вручную в настройках инструмента.
  • Расчёт выполняется один раз при старте. При изменении рыночной ситуации или параметров риска перезапустите стратегию.
  • Поскольку стратегия не торгует, её можно безопасно применять в тестовой и боевой среде исключительно как аналитический инструмент.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Volume Calculator strategy: EMA + volume confirmation.
/// Buys when price above EMA with increasing volume, sells below EMA with increasing volume.
/// </summary>
public class VolumeCalculatorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;

	private decimal _prevVolume;
	private bool _wasBullishSignal;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }

	public VolumeCalculatorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend filter period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevVolume = 0;
		_wasBullishSignal = false;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevVolume = 0;
		_wasBullishSignal = false;
		_hasPrev = false;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, ProcessCandle).Start();
	}

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

		if (_hasPrev)
		{
			var volumeUp = candle.TotalVolume > _prevVolume;
			var bullishSignal = candle.ClosePrice > emaValue && volumeUp;
			var bearishSignal = candle.ClosePrice < emaValue && volumeUp;
			var crossedUp = bullishSignal && !_wasBullishSignal;
			var crossedDown = bearishSignal && _wasBullishSignal;

			if (crossedUp && Position <= 0)
				BuyMarket();
			else if (crossedDown && Position >= 0)
				SellMarket();

			if (bullishSignal || bearishSignal)
				_wasBullishSignal = bullishSignal;
		}

		_prevVolume = candle.TotalVolume;
		_hasPrev = true;
	}
}