Multi-Factor Strategy
多因子策略结合 MACD、RSI 和两条移动平均线来确认趋势。当 MACD 线上穿信号线、RSI 低于 70、价格高于 50 周期均线且 50 均线高于 200 均线时做多;反向条件做空。
止损和止盈基于 ATR 的倍数。
细节
- 入场条件:
- 多头:
MACD > Signal&&RSI < 70&&Close > SMA50&&SMA50 > SMA200. - 空头:
MACD < Signal&&RSI > 30&&Close < SMA50&&SMA50 < SMA200.
- 多头:
- 方向:双向。
- 出场条件:ATR 止损与止盈。
- 止损:是。
- 默认值:
FastLength= 12SlowLength= 26SignalLength= 9RsiLength= 14AtrLength= 14StopAtrMultiplier= 2ProfitAtrMultiplier= 3CandleType= TimeSpan.FromMinutes(15)
- 过滤器:
- 分类:趋势跟随
- 方向:双向
- 指标:MACD, RSI, SMA, ATR
- 止损:是
- 复杂度:基础
- 时间框架:中期
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Multi-factor strategy combining MACD, RSI, ATR, and trend filters.
/// Opens long positions only on bullish MACD crossovers confirmed by RSI and long-term trend.
/// </summary>
public class MultiFactorStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _stopAtrMultiplier;
private readonly StrategyParam<decimal> _profitAtrMultiplier;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevDiff;
private bool _hasPrevDiff;
private int _cooldownRemaining;
private MovingAverageConvergenceDivergenceSignal _macd;
private RelativeStrengthIndex _rsi;
private AverageTrueRange _atr;
private SMA _sma50;
private SMA _sma200;
public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
public decimal StopAtrMultiplier { get => _stopAtrMultiplier.Value; set => _stopAtrMultiplier.Value = value; }
public decimal ProfitAtrMultiplier { get => _profitAtrMultiplier.Value; set => _profitAtrMultiplier.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MultiFactorStrategy()
{
_fastLength = Param(nameof(FastLength), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "MACD fast EMA length", "MACD");
_slowLength = Param(nameof(SlowLength), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "MACD slow EMA length", "MACD");
_signalLength = Param(nameof(SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "MACD signal EMA length", "MACD");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "ATR");
_stopAtrMultiplier = Param(nameof(StopAtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop ATR Mult", "ATR multiplier for stop", "Risk");
_profitAtrMultiplier = Param(nameof(ProfitAtrMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("Profit ATR Mult", "ATR multiplier for take profit", "Risk");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 50)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait after entries and exits", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevDiff = 0m;
_hasPrevDiff = false;
_cooldownRemaining = 0;
_macd = null;
_rsi = null;
_atr = null;
_sma50 = null;
_sma200 = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_macd = new()
{
Macd =
{
ShortMa = { Length = FastLength },
LongMa = { Length = SlowLength },
},
SignalMa = { Length = SignalLength }
};
_rsi = new() { Length = RsiLength };
_atr = new() { Length = AtrLength };
_sma50 = new() { Length = 50 };
_sma200 = new() { Length = 200 };
_prevDiff = 0m;
_hasPrevDiff = false;
_cooldownRemaining = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(3, UnitTypes.Percent),
stopLoss: new Unit(2, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var macdValue = _macd.Process(candle);
var rsiValue = _rsi.Process(candle);
var atrValue = _atr.Process(candle);
var sma50Value = _sma50.Process(new DecimalIndicatorValue(_sma50, candle.ClosePrice, candle.ServerTime) { IsFinal = true });
var sma200Value = _sma200.Process(new DecimalIndicatorValue(_sma200, candle.ClosePrice, candle.ServerTime) { IsFinal = true });
if (!_macd.IsFormed || !_rsi.IsFormed || !_atr.IsFormed || !_sma50.IsFormed || !_sma200.IsFormed)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var macdData = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (macdData.Macd is not decimal macdLine || macdData.Signal is not decimal signalLine)
return;
var diff = macdLine - signalLine;
var rsi = rsiValue.ToDecimal();
var atr = atrValue.ToDecimal();
var sma50 = sma50Value.ToDecimal();
var sma200 = sma200Value.ToDecimal();
if (!_hasPrevDiff)
{
_prevDiff = diff;
_hasPrevDiff = true;
return;
}
if (_cooldownRemaining == 0 && Position == 0)
{
var bullishCross = _prevDiff <= 0m && diff > 0m;
var bearishCross = _prevDiff >= 0m && diff < 0m;
if (bullishCross && candle.ClosePrice > sma50)
{
BuyMarket();
_cooldownRemaining = SignalCooldownBars;
}
else if (bearishCross && candle.ClosePrice < sma50)
{
SellMarket();
_cooldownRemaining = SignalCooldownBars;
}
}
_prevDiff = diff;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal, RelativeStrengthIndex, AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class multi_factor_strategy(Strategy):
def __init__(self):
super(multi_factor_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12) \
.SetGreaterThanZero() \
.SetDisplay("MACD Fast", "MACD fast EMA length", "MACD")
self._slow_length = self.Param("SlowLength", 26) \
.SetGreaterThanZero() \
.SetDisplay("MACD Slow", "MACD slow EMA length", "MACD")
self._signal_length = self.Param("SignalLength", 9) \
.SetGreaterThanZero() \
.SetDisplay("MACD Signal", "MACD signal EMA length", "MACD")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 50) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown", "Bars to wait after entries and exits", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_diff = 0.0
self._has_prev_diff = False
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(multi_factor_strategy, self).OnReseted()
self._prev_diff = 0.0
self._has_prev_diff = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(multi_factor_strategy, self).OnStarted2(time)
self._prev_diff = 0.0
self._has_prev_diff = False
self._cooldown_remaining = 0
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = self._fast_length.Value
self._macd.Macd.LongMa.Length = self._slow_length.Value
self._macd.SignalMa.Length = self._signal_length.Value
self._sma50 = SimpleMovingAverage()
self._sma50.Length = 50
self._dummy = SimpleMovingAverage()
self._dummy.Length = 2
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._dummy, self.OnProcess).Start()
def OnProcess(self, candle, dummy_value):
if candle.State != CandleStates.Finished:
return
macd_result = process_float(self._macd, candle.ClosePrice, candle.ServerTime, True)
sma50_result = process_float(self._sma50, candle.ClosePrice, candle.ServerTime, True)
if not self._macd.IsFormed or not self._sma50.IsFormed:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
macd_line = macd_result.Macd
signal_line = macd_result.Signal
if macd_line is None or signal_line is None:
return
diff = float(macd_line) - float(signal_line)
sma50 = float(sma50_result)
close = float(candle.ClosePrice)
if not self._has_prev_diff:
self._prev_diff = diff
self._has_prev_diff = True
return
if self._cooldown_remaining == 0 and self.Position == 0:
bullish_cross = self._prev_diff <= 0.0 and diff > 0.0
bearish_cross = self._prev_diff >= 0.0 and diff < 0.0
if bullish_cross and close > sma50:
self.BuyMarket()
self._cooldown_remaining = self._signal_cooldown_bars.Value
elif bearish_cross and close < sma50:
self.SellMarket()
self._cooldown_remaining = self._signal_cooldown_bars.Value
self._prev_diff = diff
def CreateClone(self):
return multi_factor_strategy()