Ver no GitHub

Estratégia LazyBot V1

Visão geral

LazyBot V1 é uma estratégia de breakout diária convertida do consultor especialista MetaTrader 5 original. Todos os dias de negociação, ele coloca um par de ordens de stop pendentes em torno da faixa de preço do dia anterior e usa um trailing stop para proteger as posições abertas. A conversão aproveita o StockSharp API de alto nível com assinaturas de velas e gerenciamento automático de pedidos.

Lógica de negociação

  1. Aguarde a conclusão de uma vela do período configurado (diariamente por padrão).
  2. Em um novo dia, opcionalmente, certifique-se de que o horário atual do servidor esteja dentro da janela de negociação permitida e pule os finais de semana.
  3. Cancele quaisquer ordens pendentes de breakout existentes criadas pela estratégia.
  4. Coloque um stop de compra acima da máxima do dia anterior e um stop de venda abaixo da mínima do dia anterior. O parâmetro Breakout Offset (pips) adiciona distância extra a ambos os níveis de ruptura.
  5. Quando qualquer uma das ordens for acionada, mantenha o stop-loss de proteção a uma distância fixa e acompanhe-o sempre que o preço avançar a favor da negociação mais do que a distância configurada do pip.
  6. Recalcule o volume para os próximos pedidos usando um tamanho de lote fixo ou o módulo de dimensionamento baseado em risco.

Parâmetros

Nome Descrição
Tipo de vela Prazo utilizado para coletar as velas de referência (diariamente por padrão).
Nome do bot Valor escrito nos comentários do pedido para facilitar o rastreamento.
Stop Loss (pips) Distância usada para o stop inicial e final.
Deslocamento de ruptura (pips) Distância extra aplicada à máxima/mínima anterior ao colocar as ordens pendentes.
Spread máximo (pips) Spread máximo permitido antes de criar novas ordens de breakout. Defina como 0 para desativar a verificação.
Use o horário de negociação Ativa o filtro de hora de início semelhante ao EA original.
Hora de início Primeira hora (inclusive) em que novos pedidos poderão ser feitos.
Hora final Hora em que novos pedidos deixam de ser agendados. Quando igual à hora de início, o filtro atua como um limite inferior simples.
% de risco de uso Permite cálculo de volume baseado em risco.
% de risco Porcentagem do patrimônio do portfólio usado para dimensionar posições quando Use Risk % está ativado.
Volume Fixo Volume de ordem fixo usado quando o dimensionamento de risco está desabilitado. Quando zero, a estratégia volta para a propriedade global Volume (o padrão é 0,01).

Gestão de risco

  • O trailing stop reflete a lógica de trailing MetaTrader, mantendo o stop loss Stop Loss (pips) longe do melhor bid/ask e apenas apertando quando um preço melhor é alcançado.
  • O filtro de spread protege a estratégia de enviar novas ordens de breakout quando o mercado estiver muito amplo.
  • O dimensionamento baseado em risco divide o risco monetário permitido (equity * Risk %) pela distância de stop expressa em unidades de preço e nunca fica abaixo do tamanho de lote fixo.

Notas adicionais

  • Os comentários dos pedidos seguem o formato BotName;SymbolId;YYYYMMDD, o que facilita a distinção dos pedidos pendentes criados em dias diferentes.
  • A estratégia assina os dados do Nível 1 para avaliar o spread atual do filtro e rastrear stops com os últimos valores de compra/venda.
  • Os trailing stops são reaplicados em cada atualização de vela e imediatamente após os preenchimentos para corresponder ao comportamento original EA.
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();
	}
}