Открыть на GitHub

Стратегия Cryptos

Обзор

Cryptos Strategy — это порт оригинального советника MetaTrader4 cryptos.mq4 на платформу StockSharp. Стратегия рассчитана прежде всего на пару ETH/USD и сочетает полосы Боллинджера с линейной взвешенной скользящей средней (LWMA), чтобы ловить пробои после периодов сжатой волатильности. Алгоритм отслеживает экстремумы за настраиваемое число свечей и умножает полученный диапазон на коэффициент, формируя цели по прибыли.

Логика торговли

  1. Определение тренда. При касании верхней полосы Боллинджера стратегия переключается в режим поиска продаж, при касании нижней — покупок. В этот момент автоматическое обновление экстремумов выключается, фиксируя текущие значения.
  2. Условия входа.
    • Продажа открывается, когда закрытие опускается ниже LWMA, активен режим продаж и нет открытой короткой позиции.
    • Покупка открывается, когда закрытие поднимается выше LWMA, активен режим покупок и нет открытой длинной позиции.
  3. Проекция диапазона. Минимумы и максимумы (автоматические или зафиксированные вручную) задают расстояние от LWMA. Этот диапазон в тиках умножается на коэффициент take-profit и используется для расчёта целевой прибыли и объёма позиции.
  4. Контроль риска. Для каждой сделки вычисляются уровни стоп-лосса и тейк-профита. Для покупок стоп располагается ниже минимума, для продаж — выше максимума. Значения пересчитываются при каждом входе и проверяются в основном цикле стратегии.
  5. Трейлинг-выходы. Если длинная позиция закрывается ниже нижней полосы Боллинджера (или короткая — выше верхней), позиция немедленно закрывается, что повторяет защитную логику оригинального советника.

Параметры

Параметр Описание
CandleType Тип свечей, используемый для всех индикаторов.
BollingerPeriod, BollingerWidth Период и множитель стандартного отклонения полос Боллинджера.
MaPeriod Период линейной взвешенной скользящей средней по медианной цене.
LookbackCandles Число свечей для автоматического поиска максимумов и минимумов.
TakeProfitRatio Множитель диапазона для вычисления тейк-профита на ETH/USD.
AlternativeTakeProfitRatio Альтернативный множитель для остальных инструментов.
RiskPerTrade Сумма (в котируемой валюте), которую стратегия готова рискнуть в одной сделке.
ValueIndex, CryptoValueIndex Мультипликаторы для перевода риска в объём позиции для обычных и крипто-инструментов.
MinVolume, MaxVolume Жёсткие границы объёма после округления к шагу биржи.
MinRangeTicks Минимально допустимый размер диапазона в тиках, чтобы избежать нулевых стопов.
SpreadPoints Ручная установка спреда в тиках (при наличии стакана берётся автоматически).
GlobalTrend Принудительное направление: 1 — продавать, 2 — покупать, 0 — автоматически.
AutoHighLow Включает автоматическое обновление экстремумов; при выключении значения замораживаются до нового касания полос.
ManualBuyTrigger, ManualSellTrigger Установите true, чтобы немедленно открыть покупку или продажу (флаг сбрасывается после исполнения).
SkipBuys, SkipSells Запрещают открытие новых длинных или коротких позиций.

Расчёт объёма

Объём повторяет формулу MT4: volume = RiskPerTrade / rangeTicks * valueIndex. Результат приводится к VolumeStep, затем ограничивается MinVolume/MaxVolume и биржевыми лимитами инструмента.

Рекомендации по использованию

  • Перед стартом стратегия проверяет капитал портфеля. Если баланс меньше RiskPerTrade * 3, торговля отключается и выводится предупреждение — аналог поведения оригинального советника.
  • Ручные флаги и управление направлением позволяют синхронизировать алгоритм с дискреционными решениями во время реальной торговли.
  • Для ETH/USD автоматически используются CryptoValueIndex и TakeProfitRatio; прочие тикеры работают с альтернативными параметрами.
  • Стопы и цели контролируются самим алгоритмом, поэтому отдельные защитные модули не требуются.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "Cryptos" MetaTrader expert.
/// Uses Bollinger Bands with a WMA trend filter. Buys when price is below WMA
/// and touches lower band; sells when price is above WMA and touches upper band.
/// </summary>
public class CryptosStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _wmaPeriod;
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerWidth;

	private ExponentialMovingAverage _bandEma;
	private ExponentialMovingAverage _trendEma;

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

	public int WmaPeriod
	{
		get => _wmaPeriod.Value;
		set => _wmaPeriod.Value = value;
	}

	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	public decimal BollingerWidth
	{
		get => _bollingerWidth.Value;
		set => _bollingerWidth.Value = value;
	}

	public CryptosStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for signals", "General");

		_wmaPeriod = Param(nameof(WmaPeriod), 55)
			.SetGreaterThanZero()
			.SetDisplay("WMA Period", "Weighted moving average period", "Indicators");

		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");

		_bollingerWidth = Param(nameof(BollingerWidth), 2m)
			.SetGreaterThanZero()
			.SetDisplay("BB Width", "Bollinger Bands deviation", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_bandEma = new ExponentialMovingAverage { Length = BollingerPeriod };
		_trendEma = new ExponentialMovingAverage { Length = WmaPeriod };

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

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

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

		if (!_bandEma.IsFormed || !_trendEma.IsFormed)
			return;

		var close = candle.ClosePrice;
		var bandOffset = bandValue * (BollingerWidth / 100m);
		var upperBand = bandValue + bandOffset;
		var lowerBand = bandValue - bandOffset;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Buy: price below WMA, touches lower band
		if (close < trendValue && close <= lowerBand)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		// Sell: price above WMA, touches upper band
		else if (close > trendValue && close >= upperBand)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		// Exit at WMA cross
		if (Position > 0 && close >= upperBand)
			SellMarket(Position);
		else if (Position < 0 && close <= lowerBand)
			BuyMarket(Math.Abs(Position));
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_bandEma = null;
		_trendEma = null;

		base.OnReseted();
	}
}