Открыть на GitHub

Стратегия Lock

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

Последовательность работы

  1. Первичный лок – как только появляются данные, стратегия открывает длинную и короткую позиции одинакового объёма. Если оба ордера исполнились, объём следующего цикла умножается на коэффициент LotExponential.
  2. Контроль тейк-профита – каждая нога хранит цену входа. Когда закрытие свечи смещается на TakeProfitPips (переведённые в тики) от точки входа, соответствующая позиция закрывается рыночным ордером. Противоположная нога остаётся открытой, сохраняя логику MQL-версии.
  3. Повторное хеджирование – если активна только одна нога (или нет позиций), стратегия сразу же открывает новую пару. При отсутствии ног базовый объём сбрасывается к LotSize перед созданием пары.
  4. Контроль объёма – метод AdjustVolume учитывает ограничения площадки: округляет объём к VolumeStep, ограничивает его MinVolume/MaxVolume и отменяет увеличение, если итоговое значение стало равным нулю.

Условие фиксации прибыли

Исходный MQL-скрипт сравнивает баланс и эквити счёта: когда баланс превышает эквити на ExcessBalanceOverEquity, а само эквити минимум на MinProfit выше последнего зафиксированного уровня, все позиции закрываются. В C# реализации балансом считается эквити, зафиксированное в момент отсутствия позиций. При срабатывании условия все ноги закрываются, baseline обновляется и новый цикл стартует с объёма LotSize.

Параметры

  • LotSize – базовый объём первого цикла (по умолчанию 0.1m).
  • TakeProfitPips – расстояние в пунктах для закрытия каждой ноги (по умолчанию 100). Значение 0 отключает автоматическое закрытие.
  • LotExponential – множитель объёма после успешного открытия обеих ног (по умолчанию 2m).
  • ExcessBalanceOverEquity – допустимый разрыв между балансом и эквити перед фиксацией (по умолчанию 3000m).
  • MinProfit – минимальный прирост эквити, необходимый для принудительного закрытия (по умолчанию 500m).
  • CandleType – таймфрейм, управляющий логикой (по умолчанию минутные свечи).

Особенности реализации

  • Размер пункта рассчитывается по Security.PriceStep и Security.Decimals, поэтому стратегия корректно работает с 3/5-значными форекс-тикерами и стандартными фьючерсами/акциями.
  • Сделки открываются рыночными ордерами, что соответствует оригинальному советнику с брокерским тейк-профитом.
  • Стратегия хранит историю всех открытых ног, что позволяет накапливать несколько позиций в каждом направлении так же, как и в исходном MQL-коде.
namespace StockSharp.Samples.Strategies;

using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Lock strategy (simplified).
/// Uses fast and slow EMA crossover with momentum confirmation.
/// </summary>
public class LockStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public LockStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Source candles", "General");

		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

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

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };

		decimal prevFast = 0, prevSlow = 0;
		bool hasPrev = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, (ICandleMessage candle, decimal fastValue, decimal slowValue) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!hasPrev)
				{
					prevFast = fastValue;
					prevSlow = slowValue;
					hasPrev = true;
					return;
				}

				if (!IsFormedAndOnlineAndAllowTrading())
				{
					prevFast = fastValue;
					prevSlow = slowValue;
					return;
				}

				// Buy on golden cross
				if (prevFast <= prevSlow && fastValue > slowValue && Position <= 0)
				{
					BuyMarket();
				}
				// Sell on death cross
				else if (prevFast >= prevSlow && fastValue < slowValue && Position >= 0)
				{
					SellMarket();
				}

				prevFast = fastValue;
				prevSlow = slowValue;
			})
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastEma);
			DrawIndicator(area, slowEma);
			DrawOwnTrades(area);
		}
	}
}