EMA 交叉结合 RSI 与距离策略
该策略使用多条 EMA 和 RSI 产生多空信号,并通过检查快速 EMA 之间的距离来确认趋势强度。
细节
- 入场条件:
- EMA5 高于 EMA13。
- EMA40 高于 EMA55。
- RSI 高于 50 且高于其 SMA。
- EMA5 与 EMA13 之间的距离高于其平均值,且 EMA40-EMA13 的距离在增加。
- 收盘价高于 EMA5。
- 多空方向:多头和空头。
- 出场条件:
- 信号变为中性或反向。
- 止损:无。
- 默认值:
EmaShortLength= 5EmaMediumLength= 13EmaLong1Length= 40EmaLong2Length= 55RsiLength= 14RsiAverageLength= 14DistanceLength= 5
- 筛选:
- 类型:趋势跟随
- 方向:双向
- 指标:EMA、RSI
- 止损:否
- 复杂度:中等
- 时间框架:任意
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
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>
/// EMA crossover with RSI and distance strategy.
/// The long signal needs EMA short above EMA medium, EMA long1 above EMA long2, RSI above 50 and above its SMA, the distance
/// between EMA short and EMA medium above its average over DistanceLength, a growing distance between EMA long1 and EMA medium
/// and the close above EMA short. The short signal mirrors it. A position is opened on its signal, reversing an opposite one,
/// and closed as soon as the signal turns neutral.
/// </summary>
public class EmaCrossoverRsiDistanceStrategy : Strategy
{
private readonly StrategyParam<int> _emaShortLength;
private readonly StrategyParam<int> _emaMediumLength;
private readonly StrategyParam<int> _emaLong1Length;
private readonly StrategyParam<int> _emaLong2Length;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _rsiAverageLength;
private readonly StrategyParam<int> _distanceLength;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _rsiAverage;
private SimpleMovingAverage _distanceAverage;
private decimal? _prevLongDistance;
/// <summary>
/// Short EMA length.
/// </summary>
public int EmaShortLength
{
get => _emaShortLength.Value;
set => _emaShortLength.Value = value;
}
/// <summary>
/// Medium EMA length.
/// </summary>
public int EmaMediumLength
{
get => _emaMediumLength.Value;
set => _emaMediumLength.Value = value;
}
/// <summary>
/// First long EMA length.
/// </summary>
public int EmaLong1Length
{
get => _emaLong1Length.Value;
set => _emaLong1Length.Value = value;
}
/// <summary>
/// Second long EMA length.
/// </summary>
public int EmaLong2Length
{
get => _emaLong2Length.Value;
set => _emaLong2Length.Value = value;
}
/// <summary>
/// RSI length.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Length of the RSI SMA.
/// </summary>
public int RsiAverageLength
{
get => _rsiAverageLength.Value;
set => _rsiAverageLength.Value = value;
}
/// <summary>
/// Length of the average of the short-medium EMA distance.
/// </summary>
public int DistanceLength
{
get => _distanceLength.Value;
set => _distanceLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public EmaCrossoverRsiDistanceStrategy()
{
_emaShortLength = Param(nameof(EmaShortLength), 5)
.SetGreaterThanZero()
.SetDisplay("EMA Short", "Short EMA length", "Indicators");
_emaMediumLength = Param(nameof(EmaMediumLength), 13)
.SetGreaterThanZero()
.SetDisplay("EMA Medium", "Medium EMA length", "Indicators");
_emaLong1Length = Param(nameof(EmaLong1Length), 40)
.SetGreaterThanZero()
.SetDisplay("EMA Long 1", "First long EMA length", "Indicators");
_emaLong2Length = Param(nameof(EmaLong2Length), 55)
.SetGreaterThanZero()
.SetDisplay("EMA Long 2", "Second long EMA length", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI length", "Indicators");
_rsiAverageLength = Param(nameof(RsiAverageLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Average", "Length of the RSI SMA", "Indicators");
_distanceLength = Param(nameof(DistanceLength), 5)
.SetGreaterThanZero()
.SetDisplay("Distance Length", "Length of the EMA distance average", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevLongDistance = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevLongDistance = null;
var emaShort = new ExponentialMovingAverage { Length = EmaShortLength };
var emaMedium = new ExponentialMovingAverage { Length = EmaMediumLength };
var emaLong1 = new ExponentialMovingAverage { Length = EmaLong1Length };
var emaLong2 = new ExponentialMovingAverage { Length = EmaLong2Length };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
_rsiAverage = new SimpleMovingAverage { Length = RsiAverageLength };
_distanceAverage = new SimpleMovingAverage { Length = DistanceLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(emaShort, emaMedium, emaLong1, emaLong2, rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, emaShort);
DrawIndicator(area, emaMedium);
DrawIndicator(area, emaLong1);
DrawIndicator(area, emaLong2);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaShort, decimal emaMedium, decimal emaLong1, decimal emaLong2, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
var rsiAverageValue = _rsiAverage.Process(new DecimalIndicatorValue(_rsiAverage, rsi, candle.OpenTime) { IsFinal = true });
var distance = Math.Abs(emaShort - emaMedium);
var distanceAverageValue = _distanceAverage.Process(new DecimalIndicatorValue(_distanceAverage, distance, candle.OpenTime) { IsFinal = true });
var longDistance = Math.Abs(emaLong1 - emaMedium);
var prevLongDistance = _prevLongDistance;
_prevLongDistance = longDistance;
if (!rsiAverageValue.IsFormed || !distanceAverageValue.IsFormed || prevLongDistance is not decimal prevDistance)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var rsiAverage = rsiAverageValue.GetValue<decimal>();
var distanceAverage = distanceAverageValue.GetValue<decimal>();
var close = candle.ClosePrice;
var strongTrend = distance > distanceAverage && longDistance > prevDistance;
var longSignal = emaShort > emaMedium && emaLong1 > emaLong2 && rsi > 50m && rsi > rsiAverage && strongTrend && close > emaShort;
var shortSignal = emaShort < emaMedium && emaLong1 < emaLong2 && rsi < 50m && rsi < rsiAverage && strongTrend && close < emaShort;
if (longSignal)
{
if (Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
}
else if (shortSignal)
{
if (Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
else if (Position > 0)
{
SellMarket(Position);
}
else if (Position < 0)
{
BuyMarket(-Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, SimpleMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class ema_crossover_rsi_distance_strategy(Strategy):
"""
EMA crossover with RSI and distance strategy.
The long signal needs EMA short above EMA medium, EMA long1 above EMA long2, RSI above 50 and above its SMA, the distance
between EMA short and EMA medium above its average over DistanceLength, a growing distance between EMA long1 and EMA medium
and the close above EMA short. The short signal mirrors it. A position is opened on its signal, reversing an opposite one,
and closed as soon as the signal turns neutral.
"""
def __init__(self):
super(ema_crossover_rsi_distance_strategy, self).__init__()
self._ema_short_length = self.Param("EmaShortLength", 5).SetGreaterThanZero().SetDisplay("EMA Short", "Short EMA length", "Indicators")
self._ema_medium_length = self.Param("EmaMediumLength", 13).SetGreaterThanZero().SetDisplay("EMA Medium", "Medium EMA length", "Indicators")
self._ema_long1_length = self.Param("EmaLong1Length", 40).SetGreaterThanZero().SetDisplay("EMA Long 1", "First long EMA length", "Indicators")
self._ema_long2_length = self.Param("EmaLong2Length", 55).SetGreaterThanZero().SetDisplay("EMA Long 2", "Second long EMA length", "Indicators")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "RSI length", "Indicators")
self._rsi_average_length = self.Param("RsiAverageLength", 14).SetGreaterThanZero().SetDisplay("RSI Average", "Length of the RSI SMA", "Indicators")
self._distance_length = self.Param("DistanceLength", 5).SetGreaterThanZero().SetDisplay("Distance Length", "Length of the EMA distance average", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._rsi_average = None
self._distance_average = None
self._prev_long_distance = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ema_crossover_rsi_distance_strategy, self).OnReseted()
self._prev_long_distance = None
def OnStarted2(self, time):
super(ema_crossover_rsi_distance_strategy, self).OnStarted2(time)
self._prev_long_distance = None
ema_short = ExponentialMovingAverage()
ema_short.Length = self._ema_short_length.Value
ema_medium = ExponentialMovingAverage()
ema_medium.Length = self._ema_medium_length.Value
ema_long1 = ExponentialMovingAverage()
ema_long1.Length = self._ema_long1_length.Value
ema_long2 = ExponentialMovingAverage()
ema_long2.Length = self._ema_long2_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
self._rsi_average = SimpleMovingAverage()
self._rsi_average.Length = self._rsi_average_length.Value
self._distance_average = SimpleMovingAverage()
self._distance_average.Length = self._distance_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema_short, ema_medium, ema_long1, ema_long2, rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema_short)
self.DrawIndicator(area, ema_medium)
self.DrawIndicator(area, ema_long1)
self.DrawIndicator(area, ema_long2)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_short_value, ema_medium_value, ema_long1_value, ema_long2_value, rsi_value):
if candle.State != CandleStates.Finished:
return
ema_short = float(ema_short_value)
ema_medium = float(ema_medium_value)
ema_long1 = float(ema_long1_value)
ema_long2 = float(ema_long2_value)
rsi = float(rsi_value)
rsi_input = DecimalIndicatorValue(self._rsi_average, Decimal(rsi), candle.OpenTime)
rsi_input.IsFinal = True
rsi_average_value = self._rsi_average.Process(rsi_input)
distance = abs(ema_short - ema_medium)
distance_input = DecimalIndicatorValue(self._distance_average, Decimal(distance), candle.OpenTime)
distance_input.IsFinal = True
distance_average_value = self._distance_average.Process(distance_input)
long_distance = abs(ema_long1 - ema_medium)
prev_long_distance = self._prev_long_distance
self._prev_long_distance = long_distance
if not rsi_average_value.IsFormed or not distance_average_value.IsFormed or prev_long_distance is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
rsi_average = float(rsi_average_value.GetValue[Decimal](None))
distance_average = float(distance_average_value.GetValue[Decimal](None))
close = float(candle.ClosePrice)
strong_trend = distance > distance_average and long_distance > prev_long_distance
long_signal = ema_short > ema_medium and ema_long1 > ema_long2 and rsi > 50 and rsi > rsi_average and strong_trend and close > ema_short
short_signal = ema_short < ema_medium and ema_long1 < ema_long2 and rsi < 50 and rsi < rsi_average and strong_trend and close < ema_short
if long_signal:
if self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal:
if self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
def CreateClone(self):
return ema_crossover_rsi_distance_strategy()