MA + RSI 巫师策略
概述
该策略是 MetaTrader 5 中 MQL/17489 文件夹内 "MQL5 Wizard MA RSI" 专家的 StockSharp 版本。原始 EA 将移动平均线过滤器与 RSI 过滤器结合,通过加权得分跨越阈值时开平仓。C# 改写后的策略保持相同结构,同时利用 StockSharp 的高级 API 与风控工具。
策略适用于任意提供 OHLCV 蜡烛数据的交易品种。它计算一个可按用户设定周期与平移的移动平均线,以及一个可选择价格源的 RSI。两个指标共同构成一个复合得分;当得分超过开仓阈值时建立仓位,当反向得分达到平仓阈值时离场。额外的距离、止损、止盈设置复刻了原 EA 中的资金管理。
指标与得分
- 移动平均线:周期、算法(简单、指数、平滑、线性加权)、价格源与平移量均可配置。收盘价高于平移后的均线时得分为 100,否则为 0。
- 相对强弱指标(RSI):周期与价格源可配置。RSI 从 50 上升至 100 的区间内,长方向得分线性增长到 100;当 RSI 低于 50 时,短方向得分同样线性增长。
- 复合得分:使用
MaWeight与RsiWeight对两个指标得分做加权平均score = (maScore * MaWeight + rsiScore * RsiWeight) / (MaWeight + RsiWeight),保证结果保持在 0 到 100 之间,与原版保持一致。 - 价格距离过滤:
PriceLevelPoints指定收盘价与平移均线之间的最小距离(按价格步长转换)。距离不足的信号被忽略。
交易规则
- 仅在蜡烛收盘时更新指标与得分。
- 当反向得分超过
ThresholdClose时,立即市价平掉现有仓位。 - 做多:在当前没有多头敞口的情况下,当多头得分 ≥
ThresholdOpen、冷却期 (ExpirationBars) 已结束并满足距离过滤时,按Volume + |Position|的数量下多单,可自动反手空头。 - 做空:逻辑与做多对称。
StartProtection根据点数参数设置止损与止盈。
风险控制
策略启动后立即调用 StartProtection。StopLevelPoints 与 TakeLevelPoints 按价格点数定义,并使用当前品种的 Security.PriceStep 转换为实际价格。设置为 0 可禁用相应保护。ExpirationBars 充当同向再次开仓前的冷却时间,对应原 EA 中挂单过期的概念。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
CandleType |
分析使用的蜡烛类型。 | 15 分钟 K 线 |
ThresholdOpen |
开仓所需的最小加权得分。 | 55 |
ThresholdClose |
平仓所需的反向得分。 | 100 |
PriceLevelPoints |
价格与平移均线的最小距离(点)。 | 0 |
StopLevelPoints |
止损距离(点)。 | 50 |
TakeLevelPoints |
止盈距离(点)。 | 50 |
ExpirationBars |
同向再次开仓的冷却周期(根)。 | 4 |
MaPeriod |
移动平均线周期。 | 20 |
MaShift |
均线平移的蜡烛数量。 | 3 |
MaMethods |
均线算法(Simple、Exponential、Smoothed、LinearWeighted)。 | Simple |
MaAppliedPrice |
均线使用的价格。 | Close |
MaWeight |
均线得分的权重。 | 0.8 |
RsiPeriod |
RSI 周期。 | 3 |
RsiAppliedPrice |
RSI 使用的价格。 | Close |
RsiWeight |
RSI 得分的权重。 | 0.5 |
说明
- 策略只处理已完成的蜡烛,忽略实时形成中的数据。
- 当两个权重同时为 0 时,将不会再有信号触发。
ExpirationBars设为 0 时,允许在同一方向上连续进场。- 由于 StockSharp 默认使用市价单,原 EA 的挂单过期逻辑在此版本中通过冷却机制体现。
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()