Открыть на GitHub

Very Blondie System

Обзор

Very Blondie System — это краткосрочная сеточная стратегия возврата к среднему, изначально реализованная в советнике MetaTrader 4 «VBS - Very Blondie System». Логика остаётся прежней: после сильного отклонения цены от ближайших экстремумов диапазона за последние PeriodX баров система сразу входит по рынку и выставляет четыре лимитных ордера Мартингейла, чтобы добирать позицию, если движение продолжится.

Данные и индикаторы

  • Основные данные: одна серия свечей, задаваемая параметром CandleType (в MT4 советник использовал таймфрейм графика).
  • Индикаторы: Highest и Lowest длиной PeriodLength, отслеживающие скользящие максимумы и минимумы для определения пробоя.
  • Лента Level1: лучшие Bid/Ask используются для выставления рыночных и лимитных ордеров с теми же отступами, что и в MT4.

Логика входа

  1. На каждом завершённом баре вычисляется максимум и минимум за последние PeriodLength свечей.
  2. Получаем текущие значения bid/ask (при отсутствии котировки берётся цена закрытия свечи).
  3. Покупка: если highest - bid > LimitPoints * PointValue, отправляется рыночная покупка базовым объёмом и четыре лимитные заявки ниже ask. Каждая заявка сдвигается на GridPoints * PointValue дальше и удваивает объём предыдущей (1×, 2×, 4×, 8×, 16×).
  4. Продажа: если bid - lowest > LimitPoints * PointValue, отправляется рыночная продажа и четыре sell-limit выше bid на тех же дистанциях и с теми же множителями объёма.
  5. Одновременно может существовать только одна «корзина». Новые сигналы игнорируются, пока не будут закрыты все позиции и сняты отложенные ордера предыдущей серии.

Управление позицией

  • Денежный профит: исходный параметр Amount суммировал OrderProfit + OrderSwap по всем сделкам. Порт использует эквивалент — совокупную позицию: (close - entryPrice) * position * conversionFactor >= ProfitTarget. При достижении порога вся позиция закрывается по рынку, лимитные заявки удаляются.
  • Lockdown / перевод в безубыток: при LockDownPoints > 0 MT4 переносил стоп каждого ордера на entry price ± Point, когда сделка уходила в плюс на LockDownPoints пунктов. В StockSharp отслеживается суммарная позиция: как только цена проходит LockDownPoints * PointValue, фиксируется уровень безубытка entryPrice ± PointValue. Если последующая свеча касается этого уровня (минимум для лонга, максимум для шорта), вся корзина закрывается по рынку и все лимитки снимаются.
  • Принудительное закрытие: остановка стратегии или срабатывание профита/безубытка всегда приводит к снятию четырёх лимитных заявок, что повторяет функцию CloseAll() из MT4.

Управление капиталом

  • Базовый объём: полностью повторяет формулу MT4 MathRound(AccountBalance()/100) / 1000. Берётся текущая стоимость портфеля (или стартовая, если сделок не было), округляется от нуля и переводится в лоты. Объём приводится к Security.VolumeStep, учитывает MinVolume/MaxVolume и при отсутствии данных портфеля откатывается к параметру Volume (или 1).
  • Сетка Мартингейла: каждый дополнительный лимитный ордер удваивает базовый объём, всего четыре уровня (1×, 2×, 4×, 8×, 16×). Все объёмы нормализуются тем же методом, чтобы избежать дробных лотов.
  • Параметр PointValue: в MT4 Point может отличаться от Security.PriceStep (особенно для 5‑значных котировок). По умолчанию PointValue вычисляется автоматически из PriceStep/Step, но при необходимости его можно задать вручную для точного соответствия оригиналу.

Параметры

Имя Описание Значение по умолчанию
PeriodLength Длина окна для расчёта максимумов/минимумов 60
LimitPoints Минимальное расстояние (в пунктах MT4) от текущей цены до экстремума диапазона для запуска корзины 1000
GridPoints Шаг (в пунктах MT4) между лимитными ордерами сетки 1500
ProfitTarget Целевой плавающий профит в валюте счёта 40
LockDownPoints Прибыль (в пунктах MT4), после которой активируется защита безубытка 0
PointValue Изменение цены, соответствующее одному пункту MT4 (0 = авто) 0
CandleType Свечная серия, управляющая стратегией TimeFrameCandle, 1 minute

Особенности порта

  • Плавающая прибыль рассчитывается по совокупной позиции, а не по сумме OrderProfit + OrderSwap, что эквивалентно оригиналу при однонаправленных сделках (как и задумывалось в MT4).
  • Перенос стоп-лосса реализован как рыночный выход на уровне безубытка; никаких отдельных OrderModify не отправляется — логика остаётся внутри стратегии.
  • Цены лимитных ордеров нормализуются через Security.ShrinkPrice. Если у инструмента нет PriceStep, необходимо задать PointValue вручную, чтобы сетка совпадала с MT4.
  • Используются только высокоуровневые вызовы (SubscribeCandles, SubscribeLevel1, BuyLimit, SellLimit и т. д.), как того требуют инструкции по конвертации.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Mean reversion strategy that trades when price deviates from a range channel.
/// Buys at lower channel, sells at upper channel, exits at channel midpoint.
/// </summary>
public class VeryBlondieSystemStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _periodLength;

	public VeryBlondieSystemStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis.", "General");

		_periodLength = Param(nameof(PeriodLength), 30)
			.SetDisplay("Period Length", "Period for Highest/Lowest channel.", "Indicators");
	}

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

	public int PeriodLength
	{
		get => _periodLength.Value;
		set => _periodLength.Value = value;
	}

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

		var highest = new Highest { Length = PeriodLength };
		var lowest = new Lowest { Length = PeriodLength };
		var sma = new SimpleMovingAverage { Length = PeriodLength };

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

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

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

		var close = candle.ClosePrice;
		var range = highestValue - lowestValue;
		if (range <= 0)
			return;

		// Percentage position within channel
		var position = (close - lowestValue) / range;

		// Exit conditions: revert to mean
		if (Position > 0 && close >= smaValue)
		{
			SellMarket();
		}
		else if (Position < 0 && close <= smaValue)
		{
			BuyMarket();
		}

		// Entry: at channel extremes
		if (Position == 0)
		{
			if (position < 0.15m)
			{
				// Near lower channel - buy for mean reversion
				BuyMarket();
			}
			else if (position > 0.85m)
			{
				// Near upper channel - sell for mean reversion
				SellMarket();
			}
		}
	}
}