Стратегия MA + RSI Wizard
Обзор
Это перенос эксперта "MQL5 Wizard MA RSI" из каталога MQL/17489 на платформу StockSharp. В оригинале Expert Advisor использует фильтр скользящей средней и фильтр RSI, а сделки выполняются когда взвешенная сумма сигналов пересекает заданные пороги. В версии на C# сохранена логика советника, при этом задействованы высокоуровневые возможности StockSharp и встроенные средства риск-менеджмента.
Стратегия способна работать на любом инструменте с OHLCV-свечами. Она рассчитывает одну скользящую среднюю с настраиваемым сдвигом и индикатор RSI с выбором ценового источника. Оба индикатора формируют составной балл: при превышении порога открытия создаётся позиция, при достижении противоположным баллом порога закрытия позиция ликвидируется. Параметры дистанций, стопов и тейков повторяют настройки оригинального эксперта.
Индикаторы и оценка
- Скользящая средняя — регулируемые период, метод (Simple, Exponential, Smoothed, LinearWeighted), источник цены и сдвиг вперёд. Если закрытие выше смещённой средней, вклад MA равен 100, иначе 0.
- RSI — регулируемые период и источник цены. Для длинного сигнала вклад линейно растёт от 0 при RSI = 50 до 100 при RSI = 100; для короткого сигнала поведение симметричное.
- Составной балл — вычисляется как взвешенное среднее
score = (maScore * MaWeight + rsiScore * RsiWeight) / (MaWeight + RsiWeight), что удерживает результат в диапазоне 0…100 аналогично реализации в MetaTrader. - Фильтр дистанции — параметр
PriceLevelPointsзадаёт минимальное расстояние между ценой закрытия и смещённой средней (в пересчёте на цену через шаг цены инструмента). Более близкие сигналы отбрасываются.
Правила торговли
- Индикаторы пересчитываются только на закрытых свечах.
- Если противоположный балл ≥
ThresholdClose, текущая позиция закрывается рыночным ордером. - Вход в лонг — возможен при отсутствии длинной позиции, когда балл длинного направления ≥
ThresholdOpen, выдержан кулдаунExpirationBars, а расстояние до средней превышает порог. Объём ордераVolume + |Position|, что позволяет мгновенно переворачиваться из шорта. - Вход в шорт — зеркально к правилам для лонга.
StartProtectionактивирует стоп-лосс и тейк-профит в абсолютных ценовых пунктах.
Риск-менеджмент
После запуска вызывается StartProtection. Расстояния StopLevelPoints и TakeLevelPoints задаются в пунктах и умножаются на Security.PriceStep. Нулевое значение выключает соответствующий барьер. ExpirationBars работает как кулдаун между входами в одном направлении и отражает параметр срока действия отложенных ордеров из исходного эксперта.
Параметры
| Параметр | Описание | Значение по умолчанию |
|---|---|---|
CandleType |
Тип свечей для анализа. | 15-минутные свечи |
ThresholdOpen |
Минимальный балл для открытия позиции. | 55 |
ThresholdClose |
Минимальный обратный балл для закрытия. | 100 |
PriceLevelPoints |
Минимальная дистанция до смещённой MA (пункты). | 0 |
StopLevelPoints |
Стоп-лосс (пункты). | 50 |
TakeLevelPoints |
Тейк-профит (пункты). | 50 |
ExpirationBars |
Кулдаун перед повторным входом в том же направлении (свечи). | 4 |
MaPeriod |
Период скользящей средней. | 20 |
MaShift |
Сдвиг средней вперёд (свечи). | 3 |
MaMethods |
Метод MA (Simple, Exponential, Smoothed, LinearWeighted). | Simple |
MaAppliedPrice |
Источник цены для MA. | Close |
MaWeight |
Вес вклада MA в общий балл. | 0.8 |
RsiPeriod |
Период RSI. | 3 |
RsiAppliedPrice |
Источник цены для RSI. | Close |
RsiWeight |
Вес вклада RSI. | 0.5 |
Примечания
- Стратегия обрабатывает только закрытые свечи и игнорирует незавершённые бары.
- При нулевых весах индикаторов торговля прекращается, так как балл не достигает порогов.
ExpirationBars = 0разрешает повторные входы без ожидания.- Поскольку в StockSharp по умолчанию используются рыночные заявки, параметр истечения отложенных ордеров реализован в виде кулдауна, а не отмены заявок.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Moving average plus RSI strategy converted from the MQL5 Wizard template.
/// The strategy computes weighted scores from a shifted moving average and RSI momentum.
/// </summary>
public class MaRsiWizardStrategy : Strategy
{
/// <summary>
/// Moving average calculation methods supported by the strategy.
/// </summary>
public enum MaMethods
{
Simple,
Exponential,
Smoothed,
LinearWeighted
}
/// <summary>
/// Price sources compatible with the indicators used in the strategy.
/// </summary>
public enum AppliedPrices
{
Close,
Open,
High,
Low,
Median,
Typical,
Weighted
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _thresholdOpen;
private readonly StrategyParam<int> _thresholdClose;
private readonly StrategyParam<decimal> _priceLevelPoints;
private readonly StrategyParam<int> _stopLevelPoints;
private readonly StrategyParam<int> _takeLevelPoints;
private readonly StrategyParam<int> _expirationBars;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _maShift;
private readonly StrategyParam<MaMethods> _maMethod;
private readonly StrategyParam<AppliedPrices> _maAppliedPrice;
private readonly StrategyParam<decimal> _maWeight;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<AppliedPrices> _rsiAppliedPrice;
private readonly StrategyParam<decimal> _rsiWeight;
private DecimalLengthIndicator _ma = null!;
private RelativeStrengthIndex _rsi = null!;
private readonly Queue<decimal> _maShiftBuffer = new();
private int _barIndex;
private int? _lastLongEntryBar;
private int? _lastShortEntryBar;
/// <summary>
/// Initializes a new instance of the <see cref="MaRsiWizardStrategy"/>.
/// </summary>
public MaRsiWizardStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame for incoming candles", "General");
_thresholdOpen = Param(nameof(ThresholdOpen), 75)
.SetRange(0, 100)
.SetDisplay("Open Threshold", "Weighted score required to open a position", "Signals")
;
_thresholdClose = Param(nameof(ThresholdClose), 100)
.SetRange(0, 100)
.SetDisplay("Close Threshold", "Weighted score required to exit an existing position", "Signals")
;
_priceLevelPoints = Param(nameof(PriceLevelPoints), 0m)
.SetDisplay("Price Level (points)", "Minimum distance between price and moving average", "Signals")
;
_stopLevelPoints = Param(nameof(StopLevelPoints), 50)
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points", "Risk")
;
_takeLevelPoints = Param(nameof(TakeLevelPoints), 50)
.SetDisplay("Take Profit (points)", "Profit target distance expressed in price points", "Risk")
;
_expirationBars = Param(nameof(ExpirationBars), 24)
.SetDisplay("Signal Cooldown (bars)", "Bars to wait before allowing a new trade in the same direction", "Signals")
;
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Moving average period", "Moving Average")
;
_maShift = Param(nameof(MaShift), 3)
.SetRange(0, 100)
.SetDisplay("MA Shift", "Lag applied to the moving average output", "Moving Average")
;
_maMethod = Param(nameof(MaMethods), MaMethods.Simple)
.SetDisplay("MA Method", "Moving average calculation method", "Moving Average");
_maAppliedPrice = Param(nameof(MaAppliedPrice), AppliedPrices.Close)
.SetDisplay("MA Source", "Price type used for the moving average", "Moving Average");
_maWeight = Param(nameof(MaWeight), 0.8m)
.SetDisplay("MA Weight", "Contribution of the moving average score", "Signals")
.SetRange(0m, 1m)
;
_rsiPeriod = Param(nameof(RsiPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI calculation length", "RSI")
;
_rsiAppliedPrice = Param(nameof(RsiAppliedPrice), AppliedPrices.Close)
.SetDisplay("RSI Source", "Price type used for RSI", "RSI");
_rsiWeight = Param(nameof(RsiWeight), 0.5m)
.SetDisplay("RSI Weight", "Contribution of the RSI score", "Signals")
.SetRange(0m, 1m)
;
}
/// <summary>
/// Type of candles used for analysis.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Weighted score required to open a new position.
/// </summary>
public int ThresholdOpen
{
get => _thresholdOpen.Value;
set => _thresholdOpen.Value = value;
}
/// <summary>
/// Weighted score required to close the current position.
/// </summary>
public int ThresholdClose
{
get => _thresholdClose.Value;
set => _thresholdClose.Value = value;
}
/// <summary>
/// Minimum price distance from the moving average expressed in points.
/// </summary>
public decimal PriceLevelPoints
{
get => _priceLevelPoints.Value;
set => _priceLevelPoints.Value = value;
}
/// <summary>
/// Stop loss distance expressed in points.
/// </summary>
public int StopLevelPoints
{
get => _stopLevelPoints.Value;
set => _stopLevelPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in points.
/// </summary>
public int TakeLevelPoints
{
get => _takeLevelPoints.Value;
set => _takeLevelPoints.Value = value;
}
/// <summary>
/// Cooldown measured in bars before a new trade in the same direction is allowed.
/// </summary>
public int ExpirationBars
{
get => _expirationBars.Value;
set => _expirationBars.Value = value;
}
/// <summary>
/// Moving average length.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Number of bars used to lag the moving average output.
/// </summary>
public int MaShift
{
get => _maShift.Value;
set => _maShift.Value = value;
}
/// <summary>
/// Moving average calculation method.
/// </summary>
public MaMethods MaMethod
{
get => _maMethod.Value;
set => _maMethod.Value = value;
}
/// <summary>
/// Price source used for the moving average.
/// </summary>
public AppliedPrices MaAppliedPrice
{
get => _maAppliedPrice.Value;
set => _maAppliedPrice.Value = value;
}
/// <summary>
/// Contribution of the moving average score in the weighted decision.
/// </summary>
public decimal MaWeight
{
get => _maWeight.Value;
set => _maWeight.Value = value;
}
/// <summary>
/// RSI calculation length.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Price source used for the RSI indicator.
/// </summary>
public AppliedPrices RsiAppliedPrice
{
get => _rsiAppliedPrice.Value;
set => _rsiAppliedPrice.Value = value;
}
/// <summary>
/// Contribution of the RSI score in the weighted decision.
/// </summary>
public decimal RsiWeight
{
get => _rsiWeight.Value;
set => _rsiWeight.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_maShiftBuffer.Clear();
_barIndex = 0;
_lastLongEntryBar = null;
_lastShortEntryBar = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_maShiftBuffer.Clear();
_barIndex = 0;
_lastLongEntryBar = null;
_lastShortEntryBar = null;
_ma = CreateMovingAverage(MaMethod, MaPeriod);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var step = Security.PriceStep ?? 1m;
Unit takeProfit = TakeLevelPoints > 0
? new Unit(TakeLevelPoints * step, UnitTypes.Absolute)
: null;
Unit stopLoss = StopLevelPoints > 0
? new Unit(StopLevelPoints * step, UnitTypes.Absolute)
: null;
if (stopLoss != null || takeProfit != null)
StartProtection(stopLoss ?? new Unit(), takeProfit ?? new Unit());
var priceArea = CreateChartArea();
if (priceArea != null)
{
DrawCandles(priceArea, subscription);
DrawIndicator(priceArea, _ma);
DrawOwnTrades(priceArea);
}
var rsiArea = CreateChartArea();
if (rsiArea != null)
{
rsiArea.Title = "RSI";
DrawIndicator(rsiArea, _rsi);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// removed IsFormedAndOnlineAndAllowTrading for backtesting
_barIndex++;
var maInput = SelectAppliedPrice(candle, MaAppliedPrice);
var maValue = _ma.Process(new DecimalIndicatorValue(_ma, maInput, candle.OpenTime) { IsFinal = true });
if (!maValue.IsFinal || maValue is not DecimalIndicatorValue maResult)
return;
var rsiInput = SelectAppliedPrice(candle, RsiAppliedPrice);
var rsiValue = _rsi.Process(new DecimalIndicatorValue(_rsi, rsiInput, candle.OpenTime) { IsFinal = true });
if (!rsiValue.IsFinal || rsiValue is not DecimalIndicatorValue rsiResult)
return;
var referenceMa = UpdateAndGetShiftedMa(maResult.Value);
if (referenceMa == null)
return;
var currentPrice = candle.ClosePrice;
var step = Security.PriceStep ?? 1m;
var priceOffset = PriceLevelPoints * step;
if (PriceLevelPoints > 0 && Math.Abs(currentPrice - referenceMa.Value) < priceOffset)
return;
var maLongSignal = currentPrice > referenceMa.Value ? 100m : 0m;
var maShortSignal = currentPrice < referenceMa.Value ? 100m : 0m;
var rsi = rsiResult.Value;
var rsiLongSignal = rsi > 50m ? Math.Min(100m, (rsi - 50m) * 2m) : 0m;
var rsiShortSignal = rsi < 50m ? Math.Min(100m, (50m - rsi) * 2m) : 0m;
var weightSum = MaWeight + RsiWeight;
if (weightSum <= 0m)
return;
var longScore = (MaWeight * maLongSignal + RsiWeight * rsiLongSignal) / weightSum;
var shortScore = (MaWeight * maShortSignal + RsiWeight * rsiShortSignal) / weightSum;
if (Position > 0 && shortScore >= ThresholdClose)
{
SellMarket(Math.Abs(Position));
}
else if (Position < 0 && longScore >= ThresholdClose)
{
BuyMarket(Math.Abs(Position));
}
var allowLong = ExpirationBars <= 0 || _lastLongEntryBar == null || _barIndex - _lastLongEntryBar >= ExpirationBars;
var allowShort = ExpirationBars <= 0 || _lastShortEntryBar == null || _barIndex - _lastShortEntryBar >= ExpirationBars;
if (Position <= 0 && longScore >= ThresholdOpen && allowLong)
{
var volume = Volume + Math.Abs(Position);
if (volume > 0)
{
BuyMarket(volume);
_lastLongEntryBar = _barIndex;
}
return;
}
if (Position >= 0 && shortScore >= ThresholdOpen && allowShort)
{
var volume = Volume + Math.Abs(Position);
if (volume > 0)
{
SellMarket(volume);
_lastShortEntryBar = _barIndex;
}
}
}
private decimal? UpdateAndGetShiftedMa(decimal maValue)
{
var shift = Math.Max(0, MaShift);
if (shift == 0)
{
return maValue;
}
_maShiftBuffer.Enqueue(maValue);
if (_maShiftBuffer.Count <= shift)
return null;
if (_maShiftBuffer.Count > shift + 1)
_maShiftBuffer.Dequeue();
return _maShiftBuffer.Count == shift + 1 ? _maShiftBuffer.Peek() : (decimal?)null;
}
private static DecimalLengthIndicator CreateMovingAverage(MaMethods method, int period)
{
return method switch
{
MaMethods.Simple => new SMA { Length = period },
MaMethods.Exponential => new EMA { Length = period },
MaMethods.Smoothed => new SmoothedMovingAverage { Length = period },
MaMethods.LinearWeighted => new WeightedMovingAverage { Length = period },
_ => new SMA { Length = period }
};
}
private static decimal SelectAppliedPrice(ICandleMessage candle, AppliedPrices price)
{
return price switch
{
AppliedPrices.Open => candle.OpenPrice,
AppliedPrices.High => candle.HighPrice,
AppliedPrices.Low => candle.LowPrice,
AppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPrices.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPrices.Weighted => (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m,
_ => candle.ClosePrice
};
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from System import Decimal
from StockSharp.Algo.Indicators import SimpleMovingAverage, ExponentialMovingAverage, SmoothedMovingAverage, WeightedMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
# MA method constants
MA_SIMPLE = 0
MA_EXPONENTIAL = 1
MA_SMOOTHED = 2
MA_LINEAR_WEIGHTED = 3
# Applied price constants
PRICE_CLOSE = 0
PRICE_OPEN = 1
PRICE_HIGH = 2
PRICE_LOW = 3
PRICE_MEDIAN = 4
PRICE_TYPICAL = 5
PRICE_WEIGHTED = 6
class ma_rsi_wizard_strategy(Strategy):
def __init__(self):
super(ma_rsi_wizard_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._threshold_open = self.Param("ThresholdOpen", 75)
self._threshold_close = self.Param("ThresholdClose", 100)
self._price_level_points = self.Param("PriceLevelPoints", 0.0)
self._stop_level_points = self.Param("StopLevelPoints", 50)
self._take_level_points = self.Param("TakeLevelPoints", 50)
self._expiration_bars = self.Param("ExpirationBars", 24)
self._ma_period = self.Param("MaPeriod", 20)
self._ma_shift = self.Param("MaShift", 3)
self._ma_method = self.Param("MaMethod", MA_SIMPLE)
self._ma_applied_price = self.Param("MaAppliedPrice", PRICE_CLOSE)
self._ma_weight = self.Param("MaWeight", 0.8)
self._rsi_period = self.Param("RsiPeriod", 3)
self._rsi_applied_price = self.Param("RsiAppliedPrice", PRICE_CLOSE)
self._rsi_weight = self.Param("RsiWeight", 0.5)
self._bar_index = 0
self._last_long_entry_bar = None
self._last_short_entry_bar = None
self._ma_shift_buffer = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def ThresholdOpen(self):
return self._threshold_open.Value
@ThresholdOpen.setter
def ThresholdOpen(self, value):
self._threshold_open.Value = value
@property
def ThresholdClose(self):
return self._threshold_close.Value
@ThresholdClose.setter
def ThresholdClose(self, value):
self._threshold_close.Value = value
@property
def PriceLevelPoints(self):
return self._price_level_points.Value
@PriceLevelPoints.setter
def PriceLevelPoints(self, value):
self._price_level_points.Value = value
@property
def StopLevelPoints(self):
return self._stop_level_points.Value
@StopLevelPoints.setter
def StopLevelPoints(self, value):
self._stop_level_points.Value = value
@property
def TakeLevelPoints(self):
return self._take_level_points.Value
@TakeLevelPoints.setter
def TakeLevelPoints(self, value):
self._take_level_points.Value = value
@property
def ExpirationBars(self):
return self._expiration_bars.Value
@ExpirationBars.setter
def ExpirationBars(self, value):
self._expiration_bars.Value = value
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
@property
def MaShift(self):
return self._ma_shift.Value
@MaShift.setter
def MaShift(self, value):
self._ma_shift.Value = value
@property
def MaMethod(self):
return self._ma_method.Value
@MaMethod.setter
def MaMethod(self, value):
self._ma_method.Value = value
@property
def MaAppliedPrice(self):
return self._ma_applied_price.Value
@MaAppliedPrice.setter
def MaAppliedPrice(self, value):
self._ma_applied_price.Value = value
@property
def MaWeight(self):
return self._ma_weight.Value
@MaWeight.setter
def MaWeight(self, value):
self._ma_weight.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiAppliedPrice(self):
return self._rsi_applied_price.Value
@RsiAppliedPrice.setter
def RsiAppliedPrice(self, value):
self._rsi_applied_price.Value = value
@property
def RsiWeight(self):
return self._rsi_weight.Value
@RsiWeight.setter
def RsiWeight(self, value):
self._rsi_weight.Value = value
def OnStarted2(self, time):
super(ma_rsi_wizard_strategy, self).OnStarted2(time)
self._bar_index = 0
self._last_long_entry_bar = None
self._last_short_entry_bar = None
self._ma_shift_buffer = []
self._ma_ind = self._create_ma(int(self.MaMethod), int(self.MaPeriod))
self._rsi_ind = RelativeStrengthIndex()
self._rsi_ind.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
sl = int(self.StopLevelPoints)
tp = int(self.TakeLevelPoints)
sl_unit = Unit(sl * step, UnitTypes.Absolute) if sl > 0 else Unit(0)
tp_unit = Unit(tp * step, UnitTypes.Absolute) if tp > 0 else Unit(0)
if sl > 0 or tp > 0:
self.StartProtection(stopLoss=sl_unit, takeProfit=tp_unit)
def _create_ma(self, method, period):
if method == MA_EXPONENTIAL:
ma = ExponentialMovingAverage()
elif method == MA_SMOOTHED:
ma = SmoothedMovingAverage()
elif method == MA_LINEAR_WEIGHTED:
ma = WeightedMovingAverage()
else:
ma = SimpleMovingAverage()
ma.Length = period
return ma
def _select_price(self, candle, price_type):
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if price_type == PRICE_OPEN:
return open_p
elif price_type == PRICE_HIGH:
return high
elif price_type == PRICE_LOW:
return low
elif price_type == PRICE_MEDIAN:
return (high + low) / 2.0
elif price_type == PRICE_TYPICAL:
return (high + low + close) / 3.0
elif price_type == PRICE_WEIGHTED:
return (high + low + 2.0 * close) / 4.0
else:
return close
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
self._bar_index += 1
close = float(candle.ClosePrice)
ma_price = self._select_price(candle, int(self.MaAppliedPrice))
ma_result = process_float(self._ma_ind, Decimal(ma_price), candle.OpenTime, True)
if not ma_result.IsFinal:
return
ma_val = float(ma_result)
rsi_price = self._select_price(candle, int(self.RsiAppliedPrice))
rsi_result = process_float(self._rsi_ind, Decimal(rsi_price), candle.OpenTime, True)
if not rsi_result.IsFinal:
return
rsi_val = float(rsi_result)
reference_ma = self._update_shifted_ma(ma_val)
if reference_ma is None:
return
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
price_offset = float(self.PriceLevelPoints) * step
if float(self.PriceLevelPoints) > 0.0 and abs(close - reference_ma) < price_offset:
return
ma_long_signal = 100.0 if close > reference_ma else 0.0
ma_short_signal = 100.0 if close < reference_ma else 0.0
rsi_long_signal = min(100.0, (rsi_val - 50.0) * 2.0) if rsi_val > 50.0 else 0.0
rsi_short_signal = min(100.0, (50.0 - rsi_val) * 2.0) if rsi_val < 50.0 else 0.0
ma_w = float(self.MaWeight)
rsi_w = float(self.RsiWeight)
weight_sum = ma_w + rsi_w
if weight_sum <= 0.0:
return
long_score = (ma_w * ma_long_signal + rsi_w * rsi_long_signal) / weight_sum
short_score = (ma_w * ma_short_signal + rsi_w * rsi_short_signal) / weight_sum
threshold_close = int(self.ThresholdClose)
threshold_open = int(self.ThresholdOpen)
expiration = int(self.ExpirationBars)
if self.Position > 0 and short_score >= threshold_close:
self.SellMarket(abs(float(self.Position)))
elif self.Position < 0 and long_score >= threshold_close:
self.BuyMarket(abs(float(self.Position)))
allow_long = expiration <= 0 or self._last_long_entry_bar is None or self._bar_index - self._last_long_entry_bar >= expiration
allow_short = expiration <= 0 or self._last_short_entry_bar is None or self._bar_index - self._last_short_entry_bar >= expiration
if self.Position <= 0 and long_score >= threshold_open and allow_long:
volume = float(self.Volume) + abs(float(self.Position))
if volume > 0:
self.BuyMarket(volume)
self._last_long_entry_bar = self._bar_index
return
if self.Position >= 0 and short_score >= threshold_open and allow_short:
volume = float(self.Volume) + abs(float(self.Position))
if volume > 0:
self.SellMarket(volume)
self._last_short_entry_bar = self._bar_index
def _update_shifted_ma(self, ma_val):
shift = max(0, int(self.MaShift))
if shift == 0:
return ma_val
self._ma_shift_buffer.append(ma_val)
if len(self._ma_shift_buffer) <= shift:
return None
while len(self._ma_shift_buffer) > shift + 1:
self._ma_shift_buffer.pop(0)
if len(self._ma_shift_buffer) == shift + 1:
return self._ma_shift_buffer[0]
return None
def OnReseted(self):
super(ma_rsi_wizard_strategy, self).OnReseted()
self._bar_index = 0
self._last_long_entry_bar = None
self._last_short_entry_bar = None
self._ma_shift_buffer = []
def CreateClone(self):
return ma_rsi_wizard_strategy()