Открыть на GitHub

Стратегия Crypto Scalper

Стратегия Crypto Scalper переносит оригинальную логику эксперта MetaTrader в экосистему StockSharp с использованием высокоуровн евых компонент. Она отслеживает бычье или медвежье пересечение быстрой линейно-взвешенной скользящей средней на рабочем таймфрейме и подтверждает сигнал фильтрами старшего таймфрейма. При совпадении условий стратегия входит по рынку и сопровождает позицию стоп- лоссом и тейк-профитом, заданными в пунктах MetaTrader.

Параметры

Имя Описание Значение по умолчанию
Primary Candle Тип свечей на основном таймфрейме. Таймфрейм 1 минута
Higher Candle Тип свечей на подтверждающем таймфрейме. Таймфрейм 15 минут
Fast LWMA Период основной линейно-взвешенной скользящей средней. 8
Higher Fast MA Период быстрой LWMA на старшем таймфрейме. 6
Higher Slow MA Период медленной LWMA на старшем таймфрейме. 85
Momentum Period Период индикатора Momentum на старшем таймфрейме. 14
Momentum Threshold Минимальное отклонение от опорного значения Momentum (уровень 100). 0.3
Momentum Reference Опорное значение Momentum для имитации масштаба MetaTrader. 100
Stop Loss (pips) Размер защитного стоп-лосса в пунктах MetaTrader. 20
Take Profit (pips) Размер тейк-профита в пунктах MetaTrader. 50
Volume Торговый объём в лотах. 0.01
MACD Fast Период быстрой EMA в MACD. 12
MACD Slow Период медленной EMA в MACD. 26
MACD Signal Период сигнальной EMA в MACD. 9

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

  1. Подписка на основной таймфрейм и расчёт быстро реагирующей LWMA.
  2. Определение точки входа при пересечении предыдущей свечой LWMA снизу вверх (лонг) или сверху вниз (шорт).
  3. Подтверждение сигнала фильтрами старшего таймфрейма:
    • Для лонга быстрая LWMA должна находиться выше медленной, для шорта — ниже.
    • Гистограмма MACD (разница между основной и сигнальной линиями) должна быть положительной для лонга и отрицательной для шорта.
    • Значение Momentum должно отклоняться от опорного уровня минимум на Momentum Threshold.
  4. При отсутствии активных заявок и допустимом направлении позиции отправляется рыночная заявка.
  5. На последующих свечах контролируется достижение уровней стоп-лосса или тейк-профита и при срабатывании позиция закрывается.

Примечания

  • Используется высокоуровневый API StockSharp и механизм Bind, что исключает ручную работу с буферами индикаторов.
  • Защитные уровни пересчитываются на каждой свече с учётом шага цены инструмента. При отсутствии настройки шага применяется запасное значение 0.0001.
  • Одновременно допускается только одна позиция: новые сигналы игнорируются, пока текущая сделка не завершена.
  • Все комментарии в коде стратегии написаны на английском языке согласно требованиям репозитория.
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;

public class CryptoScalperStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public CryptoScalperStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}