Auf GitHub ansehen

Very Blonde System Strategy

Grid-based counter-trend strategy inspired by the original "Very Blonde System" for MetaTrader. The strategy looks for a large distance between the current price and recent extremes and trades in the opposite direction.

Strategy Logic

  1. Calculate the highest high and lowest low over the last Count Bars candles.
  2. When there are no open positions:
    • If the distance from the recent high to the current price exceeds Limit ticks, buy at market.
    • If the distance from the current price to the recent low exceeds Limit ticks, sell at market.
    • After entering a position, place four additional limit orders every Grid ticks away, doubling the volume on each level.
  3. When a position exists:
    • If the total profit exceeds Amount currency units, close the position and cancel all pending orders.
    • If Lock Down is greater than zero, once price moves in favor by that many ticks the strategy activates a breakeven protection. If price returns to the entry level, all positions are closed.

Parameters

Name Description
CountBars Number of candles to search for highs and lows.
Limit Minimum distance from the extreme in ticks to open a trade.
Grid Distance in ticks between additional grid orders.
Amount Target profit in currency to close all positions.
LockDown Distance in ticks to enable breakeven protection.
CandleType Candle type used for calculations.

The strategy uses market orders for initial entries and limit orders for grid levels. All comments in the code are written in English as required.

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;
	}
}