Открыть на GitHub

Стратегия LazyBot V1

Описание

LazyBot V1 — это конвертация оригинального советника MetaTrader 5 в формате StockSharp. Стратегия работает на дневных свечах: каждый торговый день выставляется пара отложенных заявок на пробой диапазона предыдущей свечи, а открытые позиции защищаются плавающим стоп-лоссом с фиксированным расстоянием в пунктах.

Логика работы

  1. Ожидание закрытия свечи выбранного таймфрейма (по умолчанию день).
  2. В начале нового дня стратегия проверяет, не попадает ли текущий серверный час в выходные и, при необходимости, в разрешённый торговый интервал.
  3. Все ранее созданные отложенные ордера пробоя отменяются.
  4. Заявка Buy Stop размещается выше максимума предыдущего дня, а Sell Stop — ниже минимума. Параметр «Breakout Offset (pips)» добавляет дополнительное смещение от экстремумов.
  5. После активации любой заявки стоп-лосс устанавливается на заданное расстояние и подтягивается вслед за ценой, когда прибыль превышает заданный порог.
  6. Объём следующей заявки рассчитывается либо как фиксированный, либо на основе процента риска от текущей стоимости портфеля.

Параметры

Параметр Описание
Candle Type Таймфрейм для построения ориентирных свечей (по умолчанию дневной).
Bot Name Комментарий, записываемый в заявки.
Stop Loss (pips) Расстояние стоп-лосса и шага трейлинг-стопа в пунктах.
Breakout Offset (pips) Дополнительное смещение от максимумов/минимумов предыдущего дня.
Max Spread (pips) Максимально допустимый спред перед созданием новых ордеров (0 — без ограничения).
Use Trading Hours Включение фильтра по времени начала торговли.
Start Hour Час, начиная с которого разрешено размещать новые ордера.
End Hour Час окончания размещения новых ордеров. Если равен Start Hour, фильтр работает только на нижнюю границу.
Use Risk % Переключатель между фиксированным объёмом и расчётом по риску.
Risk % Процент от стоимости портфеля, используемый для расчёта объёма при активном Use Risk %.
Fixed Volume Фиксированный объём сделки. При нуле используется глобальный параметр Volume (по умолчанию 0.01).

Управление рисками

  • Трейлинг-стоп повторяет механику исходного советника: стоп-лосс удерживается на расстоянии Stop Loss (pips) от лучшего бид/аск и подтягивается только при достижении более выгодной цены.
  • Спред-фильтр предотвращает появление новых ордеров при невыгодных рыночных условиях.
  • Расчёт объёма по риску делит допустимый денежный риск (equity * Risk %) на ценовое расстояние до стопа и никогда не опускается ниже базового лота.

Дополнительные сведения

  • Комментарии ордеров имеют формат BotName;SymbolId;YYYYMMDD, что облегчает идентификацию заявок разных дней.
  • Стратегия подписывается на поток Level1 для контроля спреда и оперативного подтягивания стоп-лосса.
  • Перерасчёт трейлинг-стопа выполняется после каждой закрытой свечи и сразу после сделок, что соответствует логике оригинальной реализации.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "LazyBot V1" MetaTrader expert.
/// Daily breakout strategy using previous N-bar high/low range.
/// Buys when price breaks above previous high, sells when below previous low.
/// </summary>
public class LazyBotV1Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _lookback;

	private readonly Queue<decimal> _highs = new();
	private readonly Queue<decimal> _lows = new();

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

	public int Lookback
	{
		get => _lookback.Value;
		set => _lookback.Value = value;
	}

	public LazyBotV1Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for breakout detection", "General");

		_lookback = Param(nameof(Lookback), 30)
			.SetGreaterThanZero()
			.SetDisplay("Lookback", "Number of bars for high/low range", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_highs.Clear();
		_lows.Clear();

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

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

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

		// Build range from previous bars (not including current)
		if (_highs.Count >= Lookback)
		{
			var highs = _highs.ToArray();
			var lows = _lows.ToArray();
			decimal highest = decimal.MinValue;
			decimal lowest = decimal.MaxValue;
			foreach (var h in highs)
				if (h > highest) highest = h;
			foreach (var l in lows)
				if (l < lowest) lowest = l;

			var close = candle.ClosePrice;
			var volume = Volume;
			if (volume <= 0)
				volume = 1;
			var padding = (highest - lowest) * 0.05m;

			if (close > highest + padding)
			{
				if (Position <= 0)
					BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
			}
			else if (close < lowest - padding)
			{
				if (Position >= 0)
					SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
			}
		}

		_highs.Enqueue(candle.HighPrice);
		_lows.Enqueue(candle.LowPrice);

		if (_highs.Count > Lookback)
		{
			_highs.Dequeue();
			_lows.Dequeue();
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_highs.Clear();
		_lows.Clear();

		base.OnReseted();
	}
}