Cryptos Strategy — это порт оригинального советника MetaTrader4 cryptos.mq4 на платформу StockSharp. Стратегия рассчитана прежде всего на пару ETH/USD и сочетает полосы Боллинджера с линейной взвешенной скользящей средней (LWMA), чтобы ловить пробои после периодов сжатой волатильности. Алгоритм отслеживает экстремумы за настраиваемое число свечей и умножает полученный диапазон на коэффициент, формируя цели по прибыли.
Логика торговли
Определение тренда. При касании верхней полосы Боллинджера стратегия переключается в режим поиска продаж, при касании нижней — покупок. В этот момент автоматическое обновление экстремумов выключается, фиксируя текущие значения.
Условия входа.
Продажа открывается, когда закрытие опускается ниже LWMA, активен режим продаж и нет открытой короткой позиции.
Покупка открывается, когда закрытие поднимается выше LWMA, активен режим покупок и нет открытой длинной позиции.
Проекция диапазона. Минимумы и максимумы (автоматические или зафиксированные вручную) задают расстояние от LWMA. Этот диапазон в тиках умножается на коэффициент take-profit и используется для расчёта целевой прибыли и объёма позиции.
Контроль риска. Для каждой сделки вычисляются уровни стоп-лосса и тейк-профита. Для покупок стоп располагается ниже минимума, для продаж — выше максимума. Значения пересчитываются при каждом входе и проверяются в основном цикле стратегии.
Трейлинг-выходы. Если длинная позиция закрывается ниже нижней полосы Боллинджера (или короткая — выше верхней), позиция немедленно закрывается, что повторяет защитную логику оригинального советника.
Параметры
Параметр
Описание
CandleType
Тип свечей, используемый для всех индикаторов.
BollingerPeriod, BollingerWidth
Период и множитель стандартного отклонения полос Боллинджера.
MaPeriod
Период линейной взвешенной скользящей средней по медианной цене.
LookbackCandles
Число свечей для автоматического поиска максимумов и минимумов.
TakeProfitRatio
Множитель диапазона для вычисления тейк-профита на ETH/USD.
AlternativeTakeProfitRatio
Альтернативный множитель для остальных инструментов.
RiskPerTrade
Сумма (в котируемой валюте), которую стратегия готова рискнуть в одной сделке.
ValueIndex, CryptoValueIndex
Мультипликаторы для перевода риска в объём позиции для обычных и крипто-инструментов.
MinVolume, MaxVolume
Жёсткие границы объёма после округления к шагу биржи.
MinRangeTicks
Минимально допустимый размер диапазона в тиках, чтобы избежать нулевых стопов.
SpreadPoints
Ручная установка спреда в тиках (при наличии стакана берётся автоматически).
Включает автоматическое обновление экстремумов; при выключении значения замораживаются до нового касания полос.
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();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class cryptos_strategy(Strategy):
def __init__(self):
super(cryptos_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._wma_period = self.Param("WmaPeriod", 55)
self._bollinger_period = self.Param("BollingerPeriod", 20)
self._bollinger_width = self.Param("BollingerWidth", 2.0)
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def WmaPeriod(self):
return self._wma_period.Value
@WmaPeriod.setter
def WmaPeriod(self, value):
self._wma_period.Value = value
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@BollingerPeriod.setter
def BollingerPeriod(self, value):
self._bollinger_period.Value = value
@property
def BollingerWidth(self):
return self._bollinger_width.Value
@BollingerWidth.setter
def BollingerWidth(self, value):
self._bollinger_width.Value = value
def OnReseted(self):
super(cryptos_strategy, self).OnReseted()
def OnStarted2(self, time):
super(cryptos_strategy, self).OnStarted2(time)
band_ema = ExponentialMovingAverage()
band_ema.Length = self.BollingerPeriod
trend_ema = ExponentialMovingAverage()
trend_ema.Length = self.WmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(band_ema, trend_ema, self._process_candle).Start()
def _process_candle(self, candle, band_value, trend_value):
if candle.State != CandleStates.Finished:
return
band_val = float(band_value)
trend_val = float(trend_value)
close = float(candle.ClosePrice)
band_offset = band_val * (float(self.BollingerWidth) / 100.0)
upper_band = band_val + band_offset
lower_band = band_val - band_offset
# Buy: price below WMA, touches lower band
if close < trend_val and close <= lower_band:
if self.Position <= 0:
self.BuyMarket()
# Sell: price above WMA, touches upper band
elif close > trend_val and close >= upper_band:
if self.Position >= 0:
self.SellMarket()
# Exit at opposite band
if self.Position > 0 and close >= upper_band:
self.SellMarket()
elif self.Position < 0 and close <= lower_band:
self.BuyMarket()
def CreateClone(self):
return cryptos_strategy()