ColorMaRsiトリガー戦略
この戦略は、オリジナルのMQL5エキスパート exp_colormarsi-trigger.mq5 のStockSharpポートです。高速・低速のEMAと高速・低速のRSI値を比較します。組み合わせシグナルは -1、0、または +1 の値を取ります。前のシグナルが現在のシグナルと逆符号の場合にポジションが開かれます。
動作方法
- シグナルが正からゼロまたは負に変わると、ロングポジションが開かれ、ショートポジションが閉じられます。
- シグナルが負からゼロまたは正に変わると、ショートポジションが開かれ、ロングポジションが閉じられます。
パラメーター
- Fast EMA – 高速指数移動平均の期間。
- Slow EMA – 低速指数移動平均の期間。
- Fast RSI – 高速RSIの期間。
- Slow RSI – 低速RSIの期間。
- Candle Type – 計算に使用するローソク足の時間軸。
インジケーター
- 指数移動平均(高速と低速)
- 相対力指数(高速と低速)
完成したローソク足のみが処理されます。注文は BuyMarket と SellMarket を使って発注されます。
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;
/// <summary>
/// ColorMaRsi Trigger strategy.
/// Combines fast and slow EMA crossover with RSI crossover to generate trading signals.
/// </summary>
public class ColorMaRsiTriggerStrategy : Strategy
{
private readonly StrategyParam<int> _emaFastLength;
private readonly StrategyParam<int> _emaSlowLength;
private readonly StrategyParam<int> _rsiFastLength;
private readonly StrategyParam<int> _rsiSlowLength;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevSignal;
private int _count;
private Order _pending;
public int EmaFastLength { get => _emaFastLength.Value; set => _emaFastLength.Value = value; }
public int EmaSlowLength { get => _emaSlowLength.Value; set => _emaSlowLength.Value = value; }
public int RsiFastLength { get => _rsiFastLength.Value; set => _rsiFastLength.Value = value; }
public int RsiSlowLength { get => _rsiSlowLength.Value; set => _rsiSlowLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ColorMaRsiTriggerStrategy()
{
_emaFastLength = Param(nameof(EmaFastLength), 5)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "General");
_emaSlowLength = Param(nameof(EmaSlowLength), 10)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "General");
_rsiFastLength = Param(nameof(RsiFastLength), 3)
.SetGreaterThanZero()
.SetDisplay("Fast RSI", "Fast RSI period", "General");
_rsiSlowLength = Param(nameof(RsiSlowLength), 13)
.SetGreaterThanZero()
.SetDisplay("Slow RSI", "Slow RSI period", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevSignal = null;
_count = 0;
_pending = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevSignal = null;
_count = 0;
_pending = null;
var emaFast = new ExponentialMovingAverage { Length = EmaFastLength };
var emaSlow = new ExponentialMovingAverage { Length = EmaSlowLength };
var rsiFast = new RelativeStrengthIndex { Length = RsiFastLength };
var rsiSlow = new RelativeStrengthIndex { Length = RsiSlowLength };
SubscribeCandles(CandleType)
.Bind(emaFast, emaSlow, rsiFast, rsiSlow, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal emaFastValue, decimal emaSlowValue, decimal rsiFastValue, decimal rsiSlowValue)
{
if (candle.State != CandleStates.Finished)
return;
// Compute composite signal from EMA and RSI crossovers
var signal = 0m;
if (emaFastValue > emaSlowValue)
signal += 1;
if (emaFastValue < emaSlowValue)
signal -= 1;
if (rsiFastValue > rsiSlowValue)
signal += 1;
if (rsiFastValue < rsiSlowValue)
signal -= 1;
// Clamp signal to [-1, 1]
signal = Math.Clamp(signal, -1, 1);
// Both RSI and EMA paths must have enough finished bars before the
// previous composite sign is meaningful.
_count++;
if (_count < Math.Max(Math.Max(EmaFastLength, EmaSlowLength),
Math.Max(RsiFastLength, RsiSlowLength)) + 2)
return;
// The documented MQL5 trigger is intentionally counter-directional:
// positive -> zero/negative opens LONG; negative -> zero/positive opens SHORT.
var buy = _prevSignal is decimal oldPositive && oldPositive > 0m && signal <= 0m;
var sell = _prevSignal is decimal oldNegative && oldNegative < 0m && signal >= 0m;
_prevSignal = signal;
if (!IsFormedAndOnlineAndAllowTrading()
|| (_pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed)))
return;
if (buy)
{
if (Position < 0m)
_pending = BuyMarket(-Position + Volume);
else if (Position == 0m)
_pending = BuyMarket(Volume);
}
else if (sell)
{
if (Position > 0m)
_pending = SellMarket(Position + Volume);
else if (Position == 0m)
_pending = SellMarket(Volume);
}
}
}
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, OrderStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class color_ma_rsi_trigger_strategy(Strategy):
"""
ColorMaRsi Trigger strategy.
Combines fast and slow EMA crossover with RSI crossover to generate trading signals.
"""
def __init__(self):
super(color_ma_rsi_trigger_strategy, self).__init__()
self._ema_fast_length = self.Param("EmaFastLength", 5) \
.SetDisplay("Fast EMA", "Fast EMA period", "General")
self._ema_slow_length = self.Param("EmaSlowLength", 10) \
.SetDisplay("Slow EMA", "Slow EMA period", "General")
self._rsi_fast_length = self.Param("RsiFastLength", 3) \
.SetDisplay("Fast RSI", "Fast RSI period", "General")
self._rsi_slow_length = self.Param("RsiSlowLength", 13) \
.SetDisplay("Slow RSI", "Slow RSI period", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_signal = None
self._count = 0
self._pending = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(color_ma_rsi_trigger_strategy, self).OnReseted()
self._prev_signal = 0.0
def OnStarted2(self, time):
super(color_ma_rsi_trigger_strategy, self).OnStarted2(time)
self._prev_signal = None
self._count = 0
self._pending = None
ema_fast = ExponentialMovingAverage()
ema_fast.Length = self._ema_fast_length.Value
ema_slow = ExponentialMovingAverage()
ema_slow.Length = self._ema_slow_length.Value
rsi_fast = RelativeStrengthIndex()
rsi_fast.Length = self._rsi_fast_length.Value
rsi_slow = RelativeStrengthIndex()
rsi_slow.Length = self._rsi_slow_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema_fast, ema_slow, rsi_fast, rsi_slow, self.on_process).Start()
def on_process(self, candle, ema_fast_val, ema_slow_val, rsi_fast_val, rsi_slow_val):
if candle.State != CandleStates.Finished:
return
signal = 0.0
if ema_fast_val > ema_slow_val:
signal += 1
if ema_fast_val < ema_slow_val:
signal -= 1
if rsi_fast_val > rsi_slow_val:
signal += 1
if rsi_fast_val < rsi_slow_val:
signal -= 1
signal = max(-1.0, min(1.0, signal))
self._count += 1
warmup = max(int(self._ema_fast_length.Value), int(self._ema_slow_length.Value),
int(self._rsi_fast_length.Value), int(self._rsi_slow_length.Value)) + 2
if self._count < warmup:
return
# Documented counter-directional transition: positive->nonpositive is buy.
buy = self._prev_signal is not None and self._prev_signal > 0 and signal <= 0
sell = self._prev_signal is not None and self._prev_signal < 0 and signal >= 0
self._prev_signal = signal
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed):
return
if buy:
if self.Position < 0:
self._pending = self.BuyMarket(-self.Position + self.Volume)
elif self.Position == 0:
self._pending = self.BuyMarket(self.Volume)
elif sell:
if self.Position > 0:
self._pending = self.SellMarket(self.Position + self.Volume)
elif self.Position == 0:
self._pending = self.SellMarket(self.Volume)
def CreateClone(self):
return color_ma_rsi_trigger_strategy()