Altarius RSI 随机指标策略
概述
Altarius RSI 随机指标策略是 MetaTrader 专家顾问 AltariusRSIxampnSTOH 的移植版本。策略将两个不同周期的随机指标与一个短周期 RSI 结合:慢速随机指标负责确认趋势方向以及超买/超卖区域,快速随机指标用于衡量动量强度。平仓由 RSI 与慢速随机指标的信号线共同决定。同时保留了原始 MQL 脚本中的资金管理特性,例如在连续亏损后缩减手数以及基于权益的回撤保护。
交易逻辑
- 数据源:使用可配置的时间周期蜡烛图(默认 15 分钟),所有指标均基于收盘价计算。
- 入场条件
- 做多:慢速随机指标主线 (15,8,8) 位于信号线之上但仍低于
BuyStochasticLimit(默认 50)。快速随机指标 (10,3,3) 的主线与信号线之差绝对值大于StochasticDifferenceThreshold(默认 5),表明动量足够强。 - 做空:慢速随机指标主线位于信号线之下但仍高于
SellStochasticLimit(默认 55)。快速随机指标同样需要满足动量阈值。
- 做多:慢速随机指标主线 (15,8,8) 位于信号线之上但仍低于
- 离场条件
- 多头离场:当 RSI(周期 4) 大于
ExitRsiHigh(60),且慢速随机指标信号线较上一根蜡烛下降并且仍高于ExitStochasticHigh(70)。 - 空头离场:当 RSI 低于
ExitRsiLow(40),且慢速随机指标信号线上升至上一根蜡烛之上并且仍低于ExitStochasticLow(30)。 - 风险控制离场:若浮动盈亏跌破允许的权益回撤比例 (
MaximumRiskPercent),立即关闭所有仓位。
- 多头离场:当 RSI(周期 4) 大于
- 仓位管理:以
BaseVolume作为起始手数,在连续亏损时根据DecreaseFactor缩减下笔交易的手数;同时会依据品种最小/最大成交量及步长对下单量进行规范。
参数说明
| 参数 | 说明 |
|---|---|
BaseVolume |
基础下单手数,风险管理前的初始值。 |
MaximumRiskPercent |
当浮亏占账户权益比例超过该数值时强制平仓。 |
DecreaseFactor |
连续亏损时缩减手数所用的除数。 |
RsiPeriod |
用于离场判断的 RSI 周期。 |
SlowStochasticPeriod, SlowStochasticK, SlowStochasticD |
慢速随机指标的配置。 |
FastStochasticPeriod, FastStochasticK, FastStochasticD |
快速随机指标的配置。 |
StochasticDifferenceThreshold |
快速随机指标主线与信号线之间的最小差值,用于确认动量。 |
BuyStochasticLimit, SellStochasticLimit |
慢速随机指标入场时允许的区间上/下限。 |
ExitRsiHigh, ExitRsiLow |
触发多头或空头离场的 RSI 阈值。 |
ExitStochasticHigh, ExitStochasticLow |
确认离场的慢速随机指标信号线阈值。 |
CandleType |
指标计算所使用的蜡烛类型。 |
备注
- 策略一次只持有一个方向的仓位,与原始 EA 行为一致。
- 连续亏损及回撤保护使用 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>
/// Strategy converted from AltariusRSIxampnSTOH MQL4 expert advisor.
/// Combines dual stochastic filters with RSI based exits and dynamic position sizing.
/// </summary>
public class AltariusRsiStochasticDualStrategy : Strategy
{
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<decimal> _maximumRiskPercent;
private readonly StrategyParam<decimal> _decreaseFactor;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _slowStochPeriod;
private readonly StrategyParam<int> _slowStochK;
private readonly StrategyParam<int> _slowStochD;
private readonly StrategyParam<int> _fastStochPeriod;
private readonly StrategyParam<int> _fastStochK;
private readonly StrategyParam<int> _fastStochD;
private readonly StrategyParam<decimal> _differenceThreshold;
private readonly StrategyParam<decimal> _buyLimit;
private readonly StrategyParam<decimal> _sellLimit;
private readonly StrategyParam<decimal> _exitRsiHigh;
private readonly StrategyParam<decimal> _exitRsiLow;
private readonly StrategyParam<decimal> _exitStochHigh;
private readonly StrategyParam<decimal> _exitStochLow;
private readonly StrategyParam<DataType> _candleType;
private decimal _previousSlowSignal;
private bool _hasPreviousSlowSignal;
private decimal _lastRealizedPnL;
private int _consecutiveLosses;
/// <summary>
/// Base order volume before risk and loss adjustments.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Maximum share of account equity allowed to be lost before forcing an exit.
/// </summary>
public decimal MaximumRiskPercent
{
get => _maximumRiskPercent.Value;
set => _maximumRiskPercent.Value = value;
}
/// <summary>
/// Factor controlling how quickly the volume shrinks after consecutive losses.
/// </summary>
public decimal DecreaseFactor
{
get => _decreaseFactor.Value;
set => _decreaseFactor.Value = value;
}
/// <summary>
/// Period for the RSI exit filter.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Period for the slow stochastic oscillator used to open trades.
/// </summary>
public int SlowStochasticPeriod
{
get => _slowStochPeriod.Value;
set => _slowStochPeriod.Value = value;
}
/// <summary>
/// %K smoothing length for the slow stochastic.
/// </summary>
public int SlowStochasticK
{
get => _slowStochK.Value;
set => _slowStochK.Value = value;
}
/// <summary>
/// %D smoothing length for the slow stochastic.
/// </summary>
public int SlowStochasticD
{
get => _slowStochD.Value;
set => _slowStochD.Value = value;
}
/// <summary>
/// Period for the fast stochastic oscillator used as momentum filter.
/// </summary>
public int FastStochasticPeriod
{
get => _fastStochPeriod.Value;
set => _fastStochPeriod.Value = value;
}
/// <summary>
/// %K smoothing length for the fast stochastic.
/// </summary>
public int FastStochasticK
{
get => _fastStochK.Value;
set => _fastStochK.Value = value;
}
/// <summary>
/// %D smoothing length for the fast stochastic.
/// </summary>
public int FastStochasticD
{
get => _fastStochD.Value;
set => _fastStochD.Value = value;
}
/// <summary>
/// Minimum distance between fast stochastic main and signal lines to allow entries.
/// </summary>
public decimal StochasticDifferenceThreshold
{
get => _differenceThreshold.Value;
set => _differenceThreshold.Value = value;
}
/// <summary>
/// Upper bound on the slow stochastic main line when opening long trades.
/// </summary>
public decimal BuyStochasticLimit
{
get => _buyLimit.Value;
set => _buyLimit.Value = value;
}
/// <summary>
/// Lower bound on the slow stochastic main line when opening short trades.
/// </summary>
public decimal SellStochasticLimit
{
get => _sellLimit.Value;
set => _sellLimit.Value = value;
}
/// <summary>
/// RSI threshold that triggers exit for long positions.
/// </summary>
public decimal ExitRsiHigh
{
get => _exitRsiHigh.Value;
set => _exitRsiHigh.Value = value;
}
/// <summary>
/// RSI threshold that triggers exit for short positions.
/// </summary>
public decimal ExitRsiLow
{
get => _exitRsiLow.Value;
set => _exitRsiLow.Value = value;
}
/// <summary>
/// Stochastic level that confirms the exit of long positions.
/// </summary>
public decimal ExitStochasticHigh
{
get => _exitStochHigh.Value;
set => _exitStochHigh.Value = value;
}
/// <summary>
/// Stochastic level that confirms the exit of short positions.
/// </summary>
public decimal ExitStochasticLow
{
get => _exitStochLow.Value;
set => _exitStochLow.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public AltariusRsiStochasticDualStrategy()
{
_baseVolume = Param(nameof(BaseVolume), 1m)
.SetNotNegative()
.SetDisplay("Base Volume", "Initial volume before money management rules", "Trading")
.SetOptimize(0.1m, 2m, 0.1m);
_maximumRiskPercent = Param(nameof(MaximumRiskPercent), 0.1m)
.SetNotNegative()
.SetDisplay("Max Risk %", "Equity drawdown percentage that forces position closure", "Risk Management")
.SetOptimize(0.05m, 0.3m, 0.05m);
_decreaseFactor = Param(nameof(DecreaseFactor), 3m)
.SetNotNegative()
.SetDisplay("Decrease Factor", "Loss streak divider applied to volume", "Risk Management")
.SetOptimize(1m, 5m, 1m);
_rsiPeriod = Param(nameof(RsiPeriod), 4)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Length of RSI used for exits", "Indicators")
.SetOptimize(2, 8, 1);
_slowStochPeriod = Param(nameof(SlowStochasticPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("Slow Stochastic Period", "Main period of slow stochastic", "Indicators")
.SetOptimize(10, 25, 1);
_slowStochK = Param(nameof(SlowStochasticK), 8)
.SetGreaterThanZero()
.SetDisplay("Slow Stochastic %K", "Smoothing of %K for slow stochastic", "Indicators");
_slowStochD = Param(nameof(SlowStochasticD), 8)
.SetGreaterThanZero()
.SetDisplay("Slow Stochastic %D", "Smoothing of %D for slow stochastic", "Indicators");
_fastStochPeriod = Param(nameof(FastStochasticPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast Stochastic Period", "Main period of fast stochastic", "Indicators")
.SetOptimize(5, 15, 1);
_fastStochK = Param(nameof(FastStochasticK), 3)
.SetGreaterThanZero()
.SetDisplay("Fast Stochastic %K", "Smoothing of %K for fast stochastic", "Indicators");
_fastStochD = Param(nameof(FastStochasticD), 3)
.SetGreaterThanZero()
.SetDisplay("Fast Stochastic %D", "Smoothing of %D for fast stochastic", "Indicators");
_differenceThreshold = Param(nameof(StochasticDifferenceThreshold), 5m)
.SetNotNegative()
.SetDisplay("Momentum Threshold", "Minimum difference between fast stochastic lines", "Trading")
.SetOptimize(2m, 10m, 1m);
_buyLimit = Param(nameof(BuyStochasticLimit), 50m)
.SetDisplay("Buy Stochastic Limit", "Upper bound of slow stochastic for longs", "Trading");
_sellLimit = Param(nameof(SellStochasticLimit), 55m)
.SetDisplay("Sell Stochastic Limit", "Lower bound of slow stochastic for shorts", "Trading");
_exitRsiHigh = Param(nameof(ExitRsiHigh), 60m)
.SetDisplay("Exit RSI High", "RSI threshold to exit longs", "Exits");
_exitRsiLow = Param(nameof(ExitRsiLow), 40m)
.SetDisplay("Exit RSI Low", "RSI threshold to exit shorts", "Exits");
_exitStochHigh = Param(nameof(ExitStochasticHigh), 70m)
.SetDisplay("Exit Stochastic High", "Slow stochastic signal level confirming long exit", "Exits");
_exitStochLow = Param(nameof(ExitStochasticLow), 30m)
.SetDisplay("Exit Stochastic Low", "Slow stochastic signal level confirming short exit", "Exits");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candles used for calculations", "Market Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousSlowSignal = 0m;
_hasPreviousSlowSignal = false;
_lastRealizedPnL = PnLManager?.RealizedPnL ?? 0m;
_consecutiveLosses = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_lastRealizedPnL = PnLManager?.RealizedPnL ?? 0m;
var rsi = new RelativeStrengthIndex
{
Length = RsiPeriod,
};
var slowStochastic = new StochasticOscillator();
slowStochastic.K.Length = SlowStochasticK;
slowStochastic.D.Length = SlowStochasticD;
var fastStochastic = new StochasticOscillator();
fastStochastic.K.Length = FastStochasticK;
fastStochastic.D.Length = FastStochasticD;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(rsi, slowStochastic, fastStochastic, ProcessIndicators)
.Start();
var chartArea = CreateChartArea();
if (chartArea != null)
{
DrawCandles(chartArea, subscription);
DrawIndicator(chartArea, rsi);
DrawIndicator(chartArea, slowStochastic);
DrawIndicator(chartArea, fastStochastic);
DrawOwnTrades(chartArea);
}
}
private void ProcessIndicators(ICandleMessage candle, IIndicatorValue rsiValue, IIndicatorValue slowValue, IIndicatorValue fastValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (MaximumRiskPercent > 0m)
{
var unrealizedPnL = PnLManager?.UnrealizedPnL ?? 0m;
if (unrealizedPnL < 0m)
{
var portfolio = Portfolio;
var equity = portfolio?.CurrentValue ?? portfolio?.BeginValue ?? 0m;
if (equity > 0m)
{
var allowedLoss = equity * MaximumRiskPercent;
if (Math.Abs(unrealizedPnL) >= allowedLoss)
{
CloseCurrentPosition();
return;
}
}
}
}
if (rsiValue.IsEmpty || slowValue.IsEmpty || fastValue.IsEmpty)
return;
var rsi = rsiValue.ToDecimal();
var slow = slowValue as StochasticOscillatorValue;
var fast = fastValue as StochasticOscillatorValue;
if (slow == null || fast == null)
return;
if (slow.K is not decimal slowMainValue ||
slow.D is not decimal slowSignalValue ||
fast.K is not decimal fastMainValue ||
fast.D is not decimal fastSignalValue)
{
return;
}
if (!_hasPreviousSlowSignal)
{
_previousSlowSignal = slowSignalValue;
_hasPreviousSlowSignal = true;
return;
}
if (Position == 0m)
{
var momentum = Math.Abs(fastMainValue - fastSignalValue);
if (slowMainValue > slowSignalValue && slowMainValue < BuyStochasticLimit && momentum > StochasticDifferenceThreshold)
{
EnterPosition(Sides.Buy);
}
else if (slowMainValue < slowSignalValue && slowMainValue > SellStochasticLimit && momentum > StochasticDifferenceThreshold)
{
EnterPosition(Sides.Sell);
}
}
else if (Position > 0m)
{
if (rsi > ExitRsiHigh && slowSignalValue < _previousSlowSignal && slowSignalValue > ExitStochasticHigh)
{
ExitPosition(Sides.Buy);
}
}
else if (Position < 0m)
{
if (rsi < ExitRsiLow && slowSignalValue > _previousSlowSignal && slowSignalValue < ExitStochasticLow)
{
ExitPosition(Sides.Sell);
}
}
_previousSlowSignal = slowSignalValue;
}
private void EnterPosition(Sides side)
{
var volume = CalculateOrderVolume();
if (volume <= 0m)
return;
if (side == Sides.Buy)
{
BuyMarket(volume);
}
else
{
SellMarket(volume);
}
}
private void ExitPosition(Sides side)
{
var position = Position;
if (position == 0m)
return;
if (side == Sides.Buy && position > 0m)
{
SellMarket(position);
}
else if (side == Sides.Sell && position < 0m)
{
BuyMarket(Math.Abs(position));
}
}
private void CloseCurrentPosition()
{
var position = Position;
if (position > 0m)
{
SellMarket(position);
}
else if (position < 0m)
{
BuyMarket(Math.Abs(position));
}
}
private decimal CalculateOrderVolume()
{
var volume = BaseVolume;
if (DecreaseFactor > 0m && _consecutiveLosses > 1)
{
var reduction = volume * _consecutiveLosses / DecreaseFactor;
volume -= reduction;
}
if (volume <= 0m)
volume = BaseVolume;
var security = Security;
if (security != null)
{
var step = security.VolumeStep ?? 0m;
if (step <= 0m)
step = 0.1m;
var minVolume = security.MinVolume ?? step;
var maxVolume = security.MaxVolume;
var steps = decimal.Floor(volume / step);
if (steps < 1m)
steps = 1m;
volume = steps * step;
if (volume < minVolume)
volume = minVolume;
if (maxVolume is decimal max && max > 0m && volume > max)
volume = max;
}
return volume;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position == 0m)
{
var realizedPnL = PnLManager?.RealizedPnL ?? 0m;
var gain = realizedPnL - _lastRealizedPnL;
_lastRealizedPnL = realizedPnL;
if (gain > 0m)
{
_consecutiveLosses = 0;
}
else if (gain < 0m)
{
_consecutiveLosses++;
}
}
}
}
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, Sides
from StockSharp.Algo.Indicators import RelativeStrengthIndex, StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class altarius_rsi_stochastic_dual_strategy(Strategy):
"""
Strategy converted from AltariusRSIxampnSTOH MQL4 expert advisor.
Combines dual stochastic filters with RSI based exits and dynamic position sizing.
"""
def __init__(self):
super(altarius_rsi_stochastic_dual_strategy, self).__init__()
self._base_volume = self.Param("BaseVolume", 1.0) \
.SetDisplay("Base Volume", "Initial volume before money management rules", "Trading")
self._rsi_period = self.Param("RsiPeriod", 4) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "Length of RSI used for exits", "Indicators")
self._slow_stoch_k = self.Param("SlowStochasticK", 8) \
.SetGreaterThanZero() \
.SetDisplay("Slow Stochastic %K", "Smoothing of %K for slow stochastic", "Indicators")
self._slow_stoch_d = self.Param("SlowStochasticD", 8) \
.SetGreaterThanZero() \
.SetDisplay("Slow Stochastic %D", "Smoothing of %D for slow stochastic", "Indicators")
self._fast_stoch_k = self.Param("FastStochasticK", 3) \
.SetGreaterThanZero() \
.SetDisplay("Fast Stochastic %K", "Smoothing of %K for fast stochastic", "Indicators")
self._fast_stoch_d = self.Param("FastStochasticD", 3) \
.SetGreaterThanZero() \
.SetDisplay("Fast Stochastic %D", "Smoothing of %D for fast stochastic", "Indicators")
self._diff_threshold = self.Param("StochasticDifferenceThreshold", 5.0) \
.SetDisplay("Momentum Threshold", "Minimum difference between fast stochastic lines", "Trading")
self._buy_limit = self.Param("BuyStochasticLimit", 50.0) \
.SetDisplay("Buy Stochastic Limit", "Upper bound of slow stochastic for longs", "Trading")
self._sell_limit = self.Param("SellStochasticLimit", 55.0) \
.SetDisplay("Sell Stochastic Limit", "Lower bound of slow stochastic for shorts", "Trading")
self._exit_rsi_high = self.Param("ExitRsiHigh", 60.0) \
.SetDisplay("Exit RSI High", "RSI threshold to exit longs", "Exits")
self._exit_rsi_low = self.Param("ExitRsiLow", 40.0) \
.SetDisplay("Exit RSI Low", "RSI threshold to exit shorts", "Exits")
self._exit_stoch_high = self.Param("ExitStochasticHigh", 70.0) \
.SetDisplay("Exit Stochastic High", "Slow stochastic signal level confirming long exit", "Exits")
self._exit_stoch_low = self.Param("ExitStochasticLow", 30.0) \
.SetDisplay("Exit Stochastic Low", "Slow stochastic signal level confirming short exit", "Exits")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candles used for calculations", "Market Data")
self._prev_slow_signal = 0.0
self._has_prev_slow_signal = False
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, v): self._candle_type.Value = v
def OnReseted(self):
super(altarius_rsi_stochastic_dual_strategy, self).OnReseted()
self._prev_slow_signal = 0.0
self._has_prev_slow_signal = False
def OnStarted2(self, time):
super(altarius_rsi_stochastic_dual_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
slow_stoch = StochasticOscillator()
slow_stoch.K.Length = self._slow_stoch_k.Value
slow_stoch.D.Length = self._slow_stoch_d.Value
fast_stoch = StochasticOscillator()
fast_stoch.K.Length = self._fast_stoch_k.Value
fast_stoch.D.Length = self._fast_stoch_d.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(rsi, slow_stoch, fast_stoch, self.ProcessIndicators).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawOwnTrades(area)
def ProcessIndicators(self, candle, rsi_value, slow_value, fast_value):
if candle.State != CandleStates.Finished:
return
if rsi_value.IsEmpty or slow_value.IsEmpty or fast_value.IsEmpty:
return
rsi = float(rsi_value)
slow_k_raw = slow_value.K if hasattr(slow_value, 'K') else None
slow_d_raw = slow_value.D if hasattr(slow_value, 'D') else None
fast_k_raw = fast_value.K if hasattr(fast_value, 'K') else None
fast_d_raw = fast_value.D if hasattr(fast_value, 'D') else None
if slow_k_raw is None or slow_d_raw is None or fast_k_raw is None or fast_d_raw is None:
return
slow_k = float(slow_k_raw)
slow_d = float(slow_d_raw)
fast_k = float(fast_k_raw)
fast_d = float(fast_d_raw)
if not self._has_prev_slow_signal:
self._prev_slow_signal = slow_d
self._has_prev_slow_signal = True
return
if self.Position == 0:
momentum = abs(fast_k - fast_d)
if slow_k > slow_d and slow_k < self._buy_limit.Value and momentum > self._diff_threshold.Value:
self.BuyMarket(self.Volume)
elif slow_k < slow_d and slow_k > self._sell_limit.Value and momentum > self._diff_threshold.Value:
self.SellMarket(self.Volume)
elif self.Position > 0:
if rsi > self._exit_rsi_high.Value and slow_d < self._prev_slow_signal and slow_d > self._exit_stoch_high.Value:
self.SellMarket(self.Position)
elif self.Position < 0:
if rsi < self._exit_rsi_low.Value and slow_d > self._prev_slow_signal and slow_d < self._exit_stoch_low.Value:
self.BuyMarket(Math.Abs(self.Position))
self._prev_slow_signal = slow_d
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return altarius_rsi_stochastic_dual_strategy()