Стратегия Color Ma RSI Trigger Duplex
Стратегия переносит советник Exp_ColorMaRsi-Trigger_Duplex.mq5 на высокий уровень API StockSharp.
Внутри работают два независимых блока MaRsi-Trigger: длинный блок отвечает за открытие и закрытие
длинных позиций, а короткий блок обслуживает короткие позиции. Каждый блок оценивает состояние
кастомного индикатора, который может принимать значения +1 (бычий фон), 0 (нейтрально) или -1
(медвежий фон). Логика MetaTrader сохранена полностью, включая задержку на две завершённые свечи
перед реакцией и раздельные настройки управления капиталом для каждого направления.
Торговая идея
- Для каждого блока рассчитываются две экспоненциальные скользящие средние (быстрая и медленная)
и два RSI (быстрый и медленный) на выбранной серии свечей.
- На каждой завершённой свече индикатор возвращает
+1, если быстрые линии превышают медленные,
-1, если они ниже, и 0 в остальных случаях. Значение ограничивается диапазоном [-1, 1], как в MT5.
- Стратегия хранит историю значений индикатора. Для заданного смещения
SignalBar сравниваются значение
свечи SignalBar + 1 (переменная older) и значение свечи SignalBar (переменная recent).
- Длинный блок:
- Если
older < 0, закрывает текущую длинную позицию (если включено закрытие).
- Если
older > 0 и recent <= 0, подготавливает открытие новой длинной позиции (если включено открытие).
- Короткий блок работает зеркально:
- Если
older > 0, закрывает короткие позиции (при включённом закрытии).
- Если
older < 0 и recent >= 0, открывает новую короткую позицию (при включённом открытии).
- Дополнительные стоп-лосс и тейк-профит в шагах цены закрывают позиции при достижении уровней.
Два блока могут работать на разных таймфреймах и с разными типами цены, что позволяет воспроизвести
поведение оригинального советника или исследовать другие комбинации.
Параметры
| Параметр |
Описание |
LongCandleType, ShortCandleType |
Серия свечей для длинного и короткого блока (по умолчанию 4 часа). |
LongVolume, ShortVolume |
Объём рыночных заявок при открытии новой позиции. |
LongAllowOpen, ShortAllowOpen |
Разрешение на открытие позиций каждым блоком. |
LongAllowClose, ShortAllowClose |
Разрешение на закрытие позиций каждым блоком. |
LongStopLossPoints, ShortStopLossPoints |
Размер стоп-лосса в шагах цены (0 = выключено). |
LongTakeProfitPoints, ShortTakeProfitPoints |
Размер тейк-профита в шагах цены (0 = выключено). |
LongSignalBar, ShortSignalBar |
Количество завершённых свечей между текущей и анализируемой свечой. |
LongRsiPeriod, LongRsiLongPeriod, ShortRsiPeriod, ShortRsiLongPeriod |
Периоды быстрого и медленного RSI. |
LongMaPeriod, LongMaLongPeriod, ShortMaPeriod, ShortMaLongPeriod |
Периоды быстрых и медленных скользящих. |
LongRsiPrice, ShortRsiPrice |
Тип цены для быстрого RSI. |
LongRsiLongPrice, ShortRsiLongPrice |
Тип цены для медленного RSI. |
LongMaPrice, ShortMaPrice |
Тип цены для быстрой скользящей средней. |
LongMaLongPrice, ShortMaLongPrice |
Тип цены для медленной скользящей средней. |
LongMaType, ShortMaType |
Вид скользящей средней для быстрой линии (простая, экспоненциальная, сглаженная, взвешенная). |
LongMaLongType, ShortMaLongType |
Вид скользящей средней для медленной линии. |
Правила торговли
- Дождаться появления завершённых свечей и прогрева индикаторов.
- На каждой новой свечке обновлять значение индикатора и историю.
- Когда в истории есть минимум
SignalBar + 2 значений, проверять условия для длинных и коротких сигналов.
- Перед открытием позиции стратегия при необходимости закрывает противоположную позицию (если закрытие разрешено).
- После открытия позиции на каждой свече контролируются уровни стоп-лосса и тейк-профита.
- Заявки выставляются рыночными ордерами через методы
BuyMarket и SellMarket.
Управление рисками
- Стопы и тейки рассчитываются через
Security.PriceStep. Если шаг цены отсутствует, используется значение 1.
- Для длинных и коротких позиций задаются независимые параметры стоп-лосса и тейк-профита.
- Дополнительные защитные механизмы (трейлинг и т.п.) не используются, что соответствует поведению MT5-советника.
Замечания
- В MetaTrader заявки планировались на открытие следующей свечи. В StockSharp ордера отправляются сразу после
закрытия свечи, что приводит к тем же моментам входа/выхода.
- В MT5 были реализованы режимы управления капиталом (
LOT, BALANCE и др.). В StockSharp используются прямые
значения объёмов (LongVolume/ShortVolume).
- Параметры проскальзывания и «магические» номера из MT5 не требуются и не перенесены.
- Индикатор построен на стандартных классах StockSharp (MA и RSI) и дополнительно ограничивает результат диапазоном
[-1, 1],
как в оригинальном индикаторе ColorMaRsi-Trigger.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Recreates the Exp_ColorMaRsi-Trigger_Duplex expert advisor.
/// Combines fast/slow MA and fast/slow RSI comparisons into a color code (+1/0/-1).
/// Trades based on color code transitions.
/// </summary>
public class ColorMaRsiTriggerDuplexStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<int> _fastRsiPeriod;
private readonly StrategyParam<int> _slowRsiPeriod;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly List<decimal> _colorHistory = new();
private int _cooldownRemaining;
/// <summary>Candle type.</summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>Fast MA period.</summary>
public int FastMaPeriod { get => _fastMaPeriod.Value; set => _fastMaPeriod.Value = value; }
/// <summary>Slow MA period.</summary>
public int SlowMaPeriod { get => _slowMaPeriod.Value; set => _slowMaPeriod.Value = value; }
/// <summary>Fast RSI period.</summary>
public int FastRsiPeriod { get => _fastRsiPeriod.Value; set => _fastRsiPeriod.Value = value; }
/// <summary>Slow RSI period.</summary>
public int SlowRsiPeriod { get => _slowRsiPeriod.Value; set => _slowRsiPeriod.Value = value; }
/// <summary>Signal bar shift.</summary>
public int SignalBar { get => _signalBar.Value; set => _signalBar.Value = value; }
/// <summary>Bars to wait between reversals.</summary>
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public ColorMaRsiTriggerDuplexStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candles", "General");
_fastMaPeriod = Param(nameof(FastMaPeriod), 5)
.SetDisplay("Fast MA Period", "Fast moving average length", "Indicators");
_slowMaPeriod = Param(nameof(SlowMaPeriod), 10)
.SetDisplay("Slow MA Period", "Slow moving average length", "Indicators");
_fastRsiPeriod = Param(nameof(FastRsiPeriod), 3)
.SetDisplay("Fast RSI Period", "Fast RSI length", "Indicators");
_slowRsiPeriod = Param(nameof(SlowRsiPeriod), 13)
.SetDisplay("Slow RSI Period", "Slow RSI length", "Indicators");
_signalBar = Param(nameof(SignalBar), 1)
.SetDisplay("Signal Bar", "History shift for signal evaluation", "Strategy");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 8)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Strategy");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
lock (_colorHistory)
_colorHistory.Clear();
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
lock (_colorHistory)
_colorHistory.Clear();
_cooldownRemaining = 0;
var fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
var slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
var fastRsi = new RelativeStrengthIndex { Length = FastRsiPeriod };
var slowRsi = new RelativeStrengthIndex { Length = SlowRsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastMa, slowMa, fastRsi, slowRsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastMa);
DrawIndicator(area, slowMa);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastMaVal, decimal slowMaVal, decimal fastRsiVal, decimal slowRsiVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
// Calculate color code from MA and RSI comparisons
var score = 0m;
if (fastMaVal > slowMaVal)
score = 1m;
else if (fastMaVal < slowMaVal)
score = -1m;
if (fastRsiVal > slowRsiVal)
score += 1m;
else if (fastRsiVal < slowRsiVal)
score -= 1m;
// Clamp to [-1, 1]
if (score > 1m) score = 1m;
else if (score < -1m) score = -1m;
decimal recent;
decimal older;
lock (_colorHistory)
{
_colorHistory.Insert(0, score);
var maxHistory = Math.Max(2, SignalBar + 2);
while (_colorHistory.Count > maxHistory)
_colorHistory.RemoveAt(_colorHistory.Count - 1);
if (_colorHistory.Count <= SignalBar + 1)
return;
recent = _colorHistory[SignalBar];
older = _colorHistory[SignalBar + 1];
}
var longOpen = older == -1m && recent == 1m;
var shortOpen = older == 1m && recent == -1m;
var longExit = Position > 0 && recent < 0m;
var shortExit = Position < 0 && recent > 0m;
if (longExit)
{
SellMarket(Position);
_cooldownRemaining = SignalCooldownBars;
}
else if (shortExit)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && longOpen && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && shortOpen && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
}
}
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, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class color_ma_rsi_trigger_duplex_strategy(Strategy):
def __init__(self):
super(color_ma_rsi_trigger_duplex_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for candles", "General")
self._fast_ma_period = self.Param("FastMaPeriod", 5) \
.SetDisplay("Fast MA Period", "Fast moving average length", "Indicators")
self._slow_ma_period = self.Param("SlowMaPeriod", 10) \
.SetDisplay("Slow MA Period", "Slow moving average length", "Indicators")
self._fast_rsi_period = self.Param("FastRsiPeriod", 3) \
.SetDisplay("Fast RSI Period", "Fast RSI length", "Indicators")
self._slow_rsi_period = self.Param("SlowRsiPeriod", 13) \
.SetDisplay("Slow RSI Period", "Slow RSI length", "Indicators")
self._signal_bar = self.Param("SignalBar", 1) \
.SetDisplay("Signal Bar", "History shift for signal evaluation", "Strategy")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 8) \
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Strategy")
self._color_history = []
self._cooldown_remaining = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastMaPeriod(self):
return self._fast_ma_period.Value
@property
def SlowMaPeriod(self):
return self._slow_ma_period.Value
@property
def FastRsiPeriod(self):
return self._fast_rsi_period.Value
@property
def SlowRsiPeriod(self):
return self._slow_rsi_period.Value
@property
def SignalBar(self):
return self._signal_bar.Value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
def OnReseted(self):
super(color_ma_rsi_trigger_duplex_strategy, self).OnReseted()
self._color_history = []
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(color_ma_rsi_trigger_duplex_strategy, self).OnStarted2(time)
self._color_history = []
self._cooldown_remaining = 0
fast_ma = ExponentialMovingAverage()
fast_ma.Length = self.FastMaPeriod
slow_ma = ExponentialMovingAverage()
slow_ma.Length = self.SlowMaPeriod
fast_rsi = RelativeStrengthIndex()
fast_rsi.Length = self.FastRsiPeriod
slow_rsi = RelativeStrengthIndex()
slow_rsi.Length = self.SlowRsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ma, slow_ma, fast_rsi, slow_rsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_ma_val, slow_ma_val, fast_rsi_val, slow_rsi_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
fmv = float(fast_ma_val)
smv = float(slow_ma_val)
frv = float(fast_rsi_val)
srv = float(slow_rsi_val)
score = 0.0
if fmv > smv:
score = 1.0
elif fmv < smv:
score = -1.0
if frv > srv:
score += 1.0
elif frv < srv:
score -= 1.0
if score > 1.0:
score = 1.0
elif score < -1.0:
score = -1.0
self._color_history.insert(0, score)
max_history = max(2, self.SignalBar + 2)
while len(self._color_history) > max_history:
self._color_history.pop()
if len(self._color_history) <= self.SignalBar + 1:
return
recent = self._color_history[self.SignalBar]
older = self._color_history[self.SignalBar + 1]
long_open = older == -1.0 and recent == 1.0
short_open = older == 1.0 and recent == -1.0
long_exit = self.Position > 0 and recent < 0.0
short_exit = self.Position < 0 and recent > 0.0
if long_exit:
self.SellMarket(self.Position)
self._cooldown_remaining = self.SignalCooldownBars
elif short_exit:
self.BuyMarket(abs(self.Position))
self._cooldown_remaining = self.SignalCooldownBars
elif self._cooldown_remaining == 0 and long_open and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._cooldown_remaining = self.SignalCooldownBars
elif self._cooldown_remaining == 0 and short_open and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._cooldown_remaining = self.SignalCooldownBars
def CreateClone(self):
return color_ma_rsi_trigger_duplex_strategy()