Ver en GitHub

Estrategia del Sistema Very Blonde

Estrategia contratendencia basada en cuadrícula inspirada en el "Very Blonde System" original para MetaTrader. La estrategia busca una gran distancia entre el precio actual y los extremos recientes y opera en la dirección contraria.

Lógica de la estrategia

  1. Calcular el máximo más alto y el mínimo más bajo durante las últimas Count Bars velas.
  2. Cuando no hay posiciones abiertas:
    • Si la distancia desde el máximo reciente al precio actual supera Limit ticks, comprar a mercado.
    • Si la distancia desde el precio actual al mínimo reciente supera Limit ticks, vender a mercado.
    • Tras entrar en una posición, colocar cuatro órdenes límite adicionales cada Grid ticks, duplicando el volumen en cada nivel.
  3. Cuando existe una posición:
    • Si el beneficio total supera Amount unidades de divisa, cerrar la posición y cancelar todas las órdenes pendientes.
    • Si Lock Down es mayor que cero, una vez que el precio se mueve a favor ese número de ticks, la estrategia activa una protección de punto de equilibrio. Si el precio regresa al nivel de entrada, se cierran todas las posiciones.

Parámetros

Nombre Descripción
CountBars Número de velas para buscar máximos y mínimos.
Limit Distancia mínima desde el extremo en ticks para abrir una operación.
Grid Distancia en ticks entre órdenes de cuadrícula adicionales.
Amount Beneficio objetivo en divisa para cerrar todas las posiciones.
LockDown Distancia en ticks para activar la protección de punto de equilibrio.
CandleType Tipo de vela utilizado para los cálculos.

La estrategia usa órdenes de mercado para entradas iniciales y órdenes límite para los niveles de la cuadrícula. Todos los comentarios en el código están escritos en inglés.

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Grid strategy based on distance from recent extremes.
/// Buys when price drops far below recent high and sells when price rises far above recent low.
/// Places additional limit orders forming a martingale grid and exits on total profit.
/// </summary>
public class VeryBlondeSystemStrategy : Strategy
{
	private readonly StrategyParam<int> _countBars;
	private readonly StrategyParam<decimal> _limit;
	private readonly StrategyParam<decimal> _grid;
	private readonly StrategyParam<decimal> _amount;
	private readonly StrategyParam<decimal> _lockDown;
	private readonly StrategyParam<DataType> _candleType;
	
	private decimal _entryPrice;
	private bool _isLong;
	private bool _lockActivated;
	private decimal _lockPrice;
	
	/// <summary>
	/// Number of candles to search extremes.
	/// </summary>
	public int CountBars
	{
		get => _countBars.Value;
		set => _countBars.Value = value;
	}
	
	/// <summary>
	/// Minimum distance from recent extreme in ticks to trigger entry.
	/// </summary>
	public decimal Limit
	{
		get => _limit.Value;
		set => _limit.Value = value;
	}
	
	/// <summary>
	/// Grid distance in ticks between additional orders.
	/// </summary>
	public decimal Grid
	{
		get => _grid.Value;
		set => _grid.Value = value;
	}
	
	/// <summary>
	/// Target profit to close all positions.
	/// </summary>
	public decimal Amount
	{
		get => _amount.Value;
		set => _amount.Value = value;
	}
	
	/// <summary>
	/// Breakeven activation distance in ticks.
	/// </summary>
	public decimal LockDown
	{
		get => _lockDown.Value;
		set => _lockDown.Value = value;
	}
	
	/// <summary>
	/// Type of candles for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public VeryBlondeSystemStrategy()
	{
		_countBars = Param(nameof(CountBars), 10)
		.SetDisplay("Count Bars", "Number of candles to search extremes", "General")
		.SetGreaterThanZero()
		
		.SetOptimize(5, 30, 5);
		
		_limit = Param(nameof(Limit), 500m)
		.SetDisplay("Limit", "Minimum distance from extreme in ticks", "Trading")
		.SetGreaterThanZero();
		
		_grid = Param(nameof(Grid), 35m)
		.SetDisplay("Grid", "Grid distance in ticks", "Trading")
		.SetGreaterThanZero();
		
		_amount = Param(nameof(Amount), 40m)
		.SetDisplay("Amount", "Target profit to close all positions", "Risk")
		.SetGreaterThanZero();
		
		_lockDown = Param(nameof(LockDown), 0m)
		.SetDisplay("Lock Down", "Breakeven activation distance in ticks", "Risk");
		
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles for calculations", "General");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		
		_entryPrice = 0m;
		_isLong = false;
		_lockActivated = false;
		_lockPrice = 0m;
	}
	
	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		var highest = new Highest { Length = CountBars };
		var lowest = new Lowest { Length = CountBars };
		
		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(highest, lowest, ProcessCandle)
		.Start();
		
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, highest);
			DrawIndicator(area, lowest);
			DrawOwnTrades(area);
		}
	}
	private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
	{
		if (candle.State != CandleStates.Finished)
		return;
		
		if (!IsFormedAndOnlineAndAllowTrading())
		return;
		
		var step = Security.PriceStep ?? 1m;
		
		if (Position == 0)
		{
			CheckForOpen(candle, high, low, step);
		}
		else
		{
			CheckForClose(candle, step);
		}
	}
	
	private void CheckForOpen(ICandleMessage candle, decimal high, decimal low, decimal step)
	{
		var close = candle.ClosePrice;
		
		if (high - close > Limit * step)
		{
			OpenPosition(true, close, step);
		}
		else if (close - low > Limit * step)
		{
			OpenPosition(false, close, step);
		}
	}
	
	private void OpenPosition(bool isBuy, decimal price, decimal step)
	{
		var volume = Volume;
		if (isBuy)
		{
			BuyMarket(volume);
		}
		else
		{
			SellMarket(volume);
		}
		
		_entryPrice = price;
		_isLong = isBuy;
		_lockActivated = false;
		_lockPrice = 0m;
		
	}
	
	private void CheckForClose(ICandleMessage candle, decimal step)
	{
		var currentProfit = Position * (candle.ClosePrice - _entryPrice);
		
		if (currentProfit >= Amount)
		{
			CloseAll();
			return;
		}
		
		if (LockDown <= 0m)
		return;
		
		if (_isLong)
		{
			if (!_lockActivated && candle.ClosePrice - _entryPrice > LockDown * step)
			{
				_lockActivated = true;
				_lockPrice = _entryPrice;
			}
			else if (_lockActivated && candle.ClosePrice <= _lockPrice)
			{
				CloseAll();
			}
		}
		else
		{
			if (!_lockActivated && _entryPrice - candle.ClosePrice > LockDown * step)
			{
				_lockActivated = true;
				_lockPrice = _entryPrice;
			}
			else if (_lockActivated && candle.ClosePrice >= _lockPrice)
			{
				CloseAll();
			}
		}
	}
	
	private void CloseAll()
	{
		if (Position > 0)
			SellMarket(Math.Abs(Position));
		else if (Position < 0)
			BuyMarket(Math.Abs(Position));

		_entryPrice = 0m;
		_lockActivated = false;
		_lockPrice = 0m;
	}
}