Slime Mold RSI 策略
这是对 MQL4 专家顾问 “Slime_Mold_RSI_v1.1” 的直接移植。策略使用四个 RSI 指标(12、36、108、324)在蜡烛的中值价格上进行计算,并将它们组合成一个感知机。每个 RSI 数值从 0–100 区间归一化到 -1…+1,再乘以可调的权重,权重和穿越零轴时切换仓位方向。
工作原理
- 对每根收盘蜡烛计算中值价格,并送入长度为 12、36、108、324 的四个 RSI 指标。
- 将每个 RSI 结果归一化到 -1…+1 区间,并乘以对应的权重。默认值 (-100) 复制原脚本中的系数 (
x - 100)。 - 将四个加权输入求和,得到当前蜡烛的感知机输出。
- 将当前输出与上一根蜡烛的感知机值比较,检测是否穿越零轴并生成信号。
交易规则
- 做多入场:上一根感知机值小于零且当前值上穿零轴。策略先平掉空头,再按
Volume建立多头仓位。 - 做空入场:上一根感知机值大于零且当前值下穿零轴。策略先平掉多头,再按
Volume建立空头仓位。 - 仓位管理:没有明确的止盈或止损,只有在新的零轴穿越时才会反向。
参数
Weight1– 归一化 12 周期 RSI 的权重。Weight2– 归一化 36 周期 RSI 的权重。Weight3– 归一化 108 周期 RSI 的权重。Weight4– 归一化 324 周期 RSI 的权重。CandleType– 策略使用的蜡烛时间框架。默认使用 1 小时蜡烛。
细节
- 入场条件:加权 RSI 感知机穿越零轴。
- 多空方向:双向(首次信号后始终保持持仓)。
- 出场条件:相反方向的穿越会反向仓位。
- 止损:无。
- 默认值:
Weight1= -100Weight2= -100Weight3= -100Weight4= -100CandleType= 1 小时蜡烛
- 过滤器:
- 分类: 感知机 / 振荡指标
- 方向: 双向
- 指标: RSI(中值价格)
- 止损: 无
- 复杂度: 中等(需要四个长周期指标)
- 时间框架: 可配置(默认日内 1 小时)
- 季节性: 否
- 神经网络: 线性感知机
- 背离: 否
- 风险等级: 取决于所选仓位和权重
备注
- 即使交易被禁用,策略仍会更新感知机值,以便在重新启用交易时保持状态连续。
- 使用蜡烛中值价格与原 MetaTrader 脚本的
PRICE_MEDIAN设置保持一致。 - 策略会立即反向仓位,调整权重和交易量时应考虑潜在滑点。
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>
/// Slime Mold RSI perceptron strategy converted from MQL4.
/// The strategy sums weighted RSI inputs to generate zero-crossing signals.
/// </summary>
public class SlimeMoldRsiStrategy : Strategy
{
private readonly StrategyParam<decimal> _weight1;
private readonly StrategyParam<decimal> _weight2;
private readonly StrategyParam<decimal> _weight3;
private readonly StrategyParam<decimal> _weight4;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi12 = null!;
private RelativeStrengthIndex _rsi36 = null!;
private RelativeStrengthIndex _rsi108 = null!;
private RelativeStrengthIndex _rsi324 = null!;
private decimal? _previousPerceptron;
/// <summary>
/// Weight applied to the 12-period RSI input.
/// </summary>
public decimal Weight1
{
get => _weight1.Value;
set => _weight1.Value = value;
}
/// <summary>
/// Weight applied to the 36-period RSI input.
/// </summary>
public decimal Weight2
{
get => _weight2.Value;
set => _weight2.Value = value;
}
/// <summary>
/// Weight applied to the 108-period RSI input.
/// </summary>
public decimal Weight3
{
get => _weight3.Value;
set => _weight3.Value = value;
}
/// <summary>
/// Weight applied to the 324-period RSI input.
/// </summary>
public decimal Weight4
{
get => _weight4.Value;
set => _weight4.Value = value;
}
/// <summary>
/// Candle type used for RSI calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="SlimeMoldRsiStrategy"/> class.
/// </summary>
public SlimeMoldRsiStrategy()
{
_weight1 = Param(nameof(Weight1), -100m)
.SetDisplay("Weight 1", "Weight applied to the 12-period RSI input", "Perceptron")
.SetOptimize(-200m, 200m, 10m);
_weight2 = Param(nameof(Weight2), -100m)
.SetDisplay("Weight 2", "Weight applied to the 36-period RSI input", "Perceptron")
.SetOptimize(-200m, 200m, 10m);
_weight3 = Param(nameof(Weight3), -100m)
.SetDisplay("Weight 3", "Weight applied to the 108-period RSI input", "Perceptron")
.SetOptimize(-200m, 200m, 10m);
_weight4 = Param(nameof(Weight4), -100m)
.SetDisplay("Weight 4", "Weight applied to the 324-period RSI input", "Perceptron")
.SetOptimize(-200m, 200m, 10m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candles used in calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
// Drop cached indicator instances and perceptron history.
_rsi12 = null!;
_rsi36 = null!;
_rsi108 = null!;
_rsi324 = null!;
_previousPerceptron = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create RSI indicators for each horizon used by the original perceptron.
_rsi12 = new RelativeStrengthIndex { Length = 12 };
_rsi36 = new RelativeStrengthIndex { Length = 36 };
_rsi108 = new RelativeStrengthIndex { Length = 108 };
_rsi324 = new RelativeStrengthIndex { Length = 324 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_rsi12 is null || _rsi36 is null || _rsi108 is null || _rsi324 is null)
return;
// Median price replicates PRICE_MEDIAN used in the original script.
var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;
var input = new DecimalIndicatorValue(_rsi12, medianPrice, candle.ServerTime) { IsFinal = true };
_rsi12.Process(input);
_rsi36.Process(new DecimalIndicatorValue(_rsi36, medianPrice, candle.ServerTime) { IsFinal = true });
_rsi108.Process(new DecimalIndicatorValue(_rsi108, medianPrice, candle.ServerTime) { IsFinal = true });
_rsi324.Process(new DecimalIndicatorValue(_rsi324, medianPrice, candle.ServerTime) { IsFinal = true });
// Wait until every RSI is fully formed before evaluating signals.
if (!_rsi12.IsFormed || !_rsi36.IsFormed || !_rsi108.IsFormed || !_rsi324.IsFormed)
return;
var rsi12Value = _rsi12.GetCurrentValue();
var rsi36Value = _rsi36.GetCurrentValue();
var rsi108Value = _rsi108.GetCurrentValue();
var rsi324Value = _rsi324.GetCurrentValue();
var currentPerceptron =
(Weight1 * NormalizeRsi(rsi12Value)) +
(Weight2 * NormalizeRsi(rsi36Value)) +
(Weight3 * NormalizeRsi(rsi108Value)) +
(Weight4 * NormalizeRsi(rsi324Value));
// Initialize the history with the first complete value.
if (_previousPerceptron is null)
{
_previousPerceptron = currentPerceptron;
return;
}
var previousPerceptron = _previousPerceptron.Value;
// Even if trading is disabled, keep the state in sync with the incoming data.
// indicators already checked above via IsFormed
// Zero-crossing from negative to positive triggers a long entry.
if (previousPerceptron < 0m && currentPerceptron > 0m && Position <= 0m)
{
BuyMarket();
LogInfo($"Long entry. Previous perceptron: {previousPerceptron:F2}, current: {currentPerceptron:F2}");
}
// Zero-crossing from positive to negative triggers a short entry.
else if (previousPerceptron > 0m && currentPerceptron < 0m && Position >= 0m)
{
SellMarket();
LogInfo($"Short entry. Previous perceptron: {previousPerceptron:F2}, current: {currentPerceptron:F2}");
}
// Store the latest perceptron value for the next signal evaluation.
_previousPerceptron = currentPerceptron;
}
private static decimal NormalizeRsi(decimal rsiValue)
{
// Transform RSI from [0,100] into [-1,+1] as in the original script.
return ((rsiValue / 100m) - 0.5m) * 2m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from indicator_extensions import *
class slime_mold_rsi_strategy(Strategy):
"""Slime Mold RSI perceptron: sums weighted RSI inputs for zero-crossing signals."""
def __init__(self):
super(slime_mold_rsi_strategy, self).__init__()
self._weight1 = self.Param("Weight1", -100.0) \
.SetDisplay("Weight 1", "Weight applied to the 12-period RSI input", "Perceptron")
self._weight2 = self.Param("Weight2", -100.0) \
.SetDisplay("Weight 2", "Weight applied to the 36-period RSI input", "Perceptron")
self._weight3 = self.Param("Weight3", -100.0) \
.SetDisplay("Weight 3", "Weight applied to the 108-period RSI input", "Perceptron")
self._weight4 = self.Param("Weight4", -100.0) \
.SetDisplay("Weight 4", "Weight applied to the 324-period RSI input", "Perceptron")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for candles used in calculations", "General")
self._prev_perceptron = None
@property
def Weight1(self):
return float(self._weight1.Value)
@property
def Weight2(self):
return float(self._weight2.Value)
@property
def Weight3(self):
return float(self._weight3.Value)
@property
def Weight4(self):
return float(self._weight4.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(slime_mold_rsi_strategy, self).OnStarted2(time)
self._prev_perceptron = None
self._rsi12 = RelativeStrengthIndex()
self._rsi12.Length = 12
self._rsi36 = RelativeStrengthIndex()
self._rsi36.Length = 36
self._rsi108 = RelativeStrengthIndex()
self._rsi108.Length = 108
self._rsi324 = RelativeStrengthIndex()
self._rsi324.Length = 324
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
median_price = (candle.HighPrice + candle.LowPrice) / 2
mp = Decimal(float(median_price))
t = candle.ServerTime
r12_val = process_float(self._rsi12, mp, t, True)
r36_val = process_float(self._rsi36, mp, t, True)
r108_val = process_float(self._rsi108, mp, t, True)
r324_val = process_float(self._rsi324, mp, t, True)
if not self._rsi12.IsFormed or not self._rsi36.IsFormed or not self._rsi108.IsFormed or not self._rsi324.IsFormed:
return
r12 = float(r12_val.Value)
r36 = float(r36_val.Value)
r108 = float(r108_val.Value)
r324 = float(r324_val.Value)
current = (self.Weight1 * self._norm(r12) +
self.Weight2 * self._norm(r36) +
self.Weight3 * self._norm(r108) +
self.Weight4 * self._norm(r324))
if self._prev_perceptron is None:
self._prev_perceptron = current
return
prev = self._prev_perceptron
if prev < 0 and current > 0 and self.Position <= 0:
self.BuyMarket()
elif prev > 0 and current < 0 and self.Position >= 0:
self.SellMarket()
self._prev_perceptron = current
def _norm(self, rsi_val):
return (rsi_val / 100.0 - 0.5) * 2.0
def OnReseted(self):
super(slime_mold_rsi_strategy, self).OnReseted()
self._prev_perceptron = None
def CreateClone(self):
return slime_mold_rsi_strategy()