Ver no GitHub

Estratégia Tweezer Bottom

O Tweezer Bottom é um padrão de reversão de duas velas que aparece após um declínio. Ambas as velas compartilham uma mínima similar, sinalizando que os vendedores falharam em empurrar além daquele nível.

Os testes indicam um retorno anual médio de aproximadamente 184%. Funciona melhor no mercado de criptomoedas.

Esta estratégia entra comprada após a segunda vela confirmar o fundo compartilhado, antecipando um repique à medida que a pressão vendedora se esgota.

Os stops são colocados logo abaixo da mínima comum para gerenciar o risco, e a posição encerra se o preço não conseguir subir.

Detalhes

  • Critérios de entrada: correspondência de padrão
  • Comprado/Vendido: Ambos
  • Critérios de saída: stop-loss ou sinal oposto
  • Stops: Sim, baseado em percentual
  • Valores padrão:
    • CandleType = 15 minutos
    • StopLoss = 2%
  • Filtros:
    • Categoria: Padrão
    • Direção: Ambos
    • Indicadores: Candlestick
    • Stops: Sim
    • Complexidade: Intermediário
    • Período: Intradiário
    • Sazonalidade: Não
    • Redes neurais: Não
    • Divergência: Não
    • Nível de risco: Médio
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Tweezer Bottom strategy.
/// Enters long on Tweezer Bottom (bearish then bullish with matching lows).
/// Enters short on Tweezer Top (bullish then bearish with matching highs).
/// Uses SMA for exit confirmation.
/// Uses cooldown to control trade frequency.
/// </summary>
public class TweezerBottomStrategy : Strategy
{
	private readonly StrategyParam<decimal> _tolerancePercent;
	private readonly StrategyParam<int> _maLength;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;

	private ICandleMessage _prevCandle;
	private int _cooldown;

	/// <summary>
	/// Tolerance for matching lows/highs.
	/// </summary>
	public decimal TolerancePercent
	{
		get => _tolerancePercent.Value;
		set => _tolerancePercent.Value = value;
	}

	/// <summary>
	/// MA period for exit.
	/// </summary>
	public int MaLength
	{
		get => _maLength.Value;
		set => _maLength.Value = value;
	}

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

	/// <summary>
	/// Cooldown bars.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Constructor.
	/// </summary>
	public TweezerBottomStrategy()
	{
		_tolerancePercent = Param(nameof(TolerancePercent), 0.1m)
			.SetRange(0.05m, 1m)
			.SetDisplay("Tolerance %", "Max diff between lows/highs", "Pattern");

		_maLength = Param(nameof(MaLength), 20)
			.SetRange(10, 50)
			.SetDisplay("MA Length", "Period of SMA for exit", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");

		_cooldownBars = Param(nameof(CooldownBars), 500)
			.SetRange(1, 1000)
			.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevCandle = null;
		_cooldown = default;
	}

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

		_prevCandle = null;
		_cooldown = 0;

		var sma = new SimpleMovingAverage { Length = MaLength };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_prevCandle == null)
		{
			_prevCandle = candle;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevCandle = candle;
			return;
		}

		var lowTolerance = _prevCandle.LowPrice * (TolerancePercent / 100m);
		var highTolerance = _prevCandle.HighPrice * (TolerancePercent / 100m);

		// Tweezer Bottom: prev bearish, current bullish, matching lows
		var isTweezerBottom =
			_prevCandle.ClosePrice < _prevCandle.OpenPrice &&
			candle.ClosePrice > candle.OpenPrice &&
			Math.Abs(_prevCandle.LowPrice - candle.LowPrice) <= lowTolerance;

		// Tweezer Top: prev bullish, current bearish, matching highs
		var isTweezerTop =
			_prevCandle.ClosePrice > _prevCandle.OpenPrice &&
			candle.ClosePrice < candle.OpenPrice &&
			Math.Abs(_prevCandle.HighPrice - candle.HighPrice) <= highTolerance;

		if (Position == 0 && isTweezerBottom)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}
		else if (Position == 0 && isTweezerTop)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		else if (Position > 0 && candle.ClosePrice < smaValue)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		else if (Position < 0 && candle.ClosePrice > smaValue)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}

		_prevCandle = candle;
	}
}