GitHub で見る
戦略 Universal Signal Demo
この戦略は、StockSharp の高レベル API を使用して、MetaTrader 5「ユニバーサル シグナル」エキスパートを複製します。 8 つの重み付けされた市場パターンを評価し、それらを 1 つの複合スコアに集約します。スコアが設定可能なしきい値を超えると、ストラテジーはロングポジションとショートポジションをオープンまたはクローズし、オプションで設定されたバー数の後に期限切れになる保留中の指値注文を使用します。
Strategy Parameters
CandleType – 分析に使用されるローソク足データ。
SignalThresholdOpen – ポジションをオープンするために必要な最小複合スコア。
SignalThresholdClose – 既存のポジションから抜け出すには相手のスコアが必要です。
PriceLevel – 保留中の指値エントリーを配置するための価格オフセット (0 は市場執行を意味します)。
StopLevel / TakeLevel – 内蔵保護モジュールによって使用される絶対的なストップロスとテイクプロフィットの距離。
SignalExpiration – まだアクティブな保留中のエントリがキャンセルされるまでのバーの数。
Pattern0Weight … Pattern7Weight – 集計前に各パターンに適用される重み。
UniversalWeight – すべてのパターン寄与の合計に適用される最終乗数。
ShortMaPeriod、LongMaPeriod、RsiPeriod、BollingerPeriod、BollingerWidth、TrendSmaPeriod、VolumeSmaPeriod – パターン チェック内で使用されるインジケーター設定。
取引ロジック
- 設定されたキャンドル ストリームをサブスクライブし、EMA、RSI、MACD シグナル、Bollinger バンド、およびサポートされる SMA をバインドします。
- ローソク足が完成するたびに、8 つのブール パターン (トレンドの調整、RSI の勢い、MACD のヒストグラム、Bollinger の位置、ローソク足の方向、ボリュームの拡大) を計算します。
- 各パターンにその重みを乗算し、寄与を合計し、全体的な重みを適用して最終スコアを取得します。
- スコアが反対方向に決済しきい値を超えた場合、オープンポジションを決済します。
- スコアが開始しきい値を超えたときに、新しいロング ポジションまたはショート ポジションをオープンします。
PriceLevel がプラスの場合、構成された距離だけオフセットされた指値注文を送信し、SignalExpiration バーの後に自動的にキャンセルします。
StartProtection は、StockSharp のリスク管理ヘルパーを使用して、すべてのポジションに対して固定のストップロスとテイクプロフィットのレベルを設定します。
この変換では、StockSharp のコーディング規約とインジケーターベースの処理に従いながら、元の MQL5 エキスパートの柔軟な重み付けワークフローが維持されます。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Universal Signal Demo strategy: Multi-indicator scoring.
/// Combines RSI and EMA signals to generate aggregate score for entries.
/// </summary>
public class UniversalSignalDemoStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _signalCooldownCandles;
private int _prevScore;
private int _candlesSinceTrade;
private bool _hasPrevScore;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public UniversalSignalDemoStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_emaPeriod = Param(nameof(EmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA period", "Indicators");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevScore = 0;
_candlesSinceTrade = SignalCooldownCandles;
_hasPrevScore = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevScore = 0;
_candlesSinceTrade = SignalCooldownCandles;
_hasPrevScore = false;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
var close = candle.ClosePrice;
var score = 0;
// RSI signal
if (rsiValue < 30) score += 2;
else if (rsiValue < 45) score += 1;
else if (rsiValue > 70) score -= 2;
else if (rsiValue > 55) score -= 1;
// EMA signal
if (close > emaValue) score += 1;
else if (close < emaValue) score -= 1;
// Candle direction
if (candle.ClosePrice > candle.OpenPrice) score += 1;
else if (candle.ClosePrice < candle.OpenPrice) score -= 1;
var crossedUp = (!_hasPrevScore || _prevScore < 2) && score >= 2;
var crossedDown = (!_hasPrevScore || _prevScore > -2) && score <= -2;
if (crossedUp && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (crossedDown && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
_prevScore = score;
_hasPrevScore = true;
}
}
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 RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class universal_signal_demo_strategy(Strategy):
def __init__(self):
super(universal_signal_demo_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._rsi_period = self.Param("RsiPeriod", 21)
self._ema_period = self.Param("EmaPeriod", 50)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 4)
self._prev_score = 0
self._candles_since_trade = 4
self._has_prev_score = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(universal_signal_demo_strategy, self).OnReseted()
self._prev_score = 0
self._candles_since_trade = self.SignalCooldownCandles
self._has_prev_score = False
def OnStarted2(self, time):
super(universal_signal_demo_strategy, self).OnStarted2(time)
self._prev_score = 0
self._candles_since_trade = self.SignalCooldownCandles
self._has_prev_score = False
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, ema, self._process_candle).Start()
def _process_candle(self, candle, rsi_value, ema_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
close = float(candle.ClosePrice)
rsi_val = float(rsi_value)
ema_val = float(ema_value)
score = 0
# RSI signal
if rsi_val < 30:
score += 2
elif rsi_val < 45:
score += 1
elif rsi_val > 70:
score -= 2
elif rsi_val > 55:
score -= 1
# EMA signal
if close > ema_val:
score += 1
elif close < ema_val:
score -= 1
# Candle direction
if float(candle.ClosePrice) > float(candle.OpenPrice):
score += 1
elif float(candle.ClosePrice) < float(candle.OpenPrice):
score -= 1
crossed_up = (not self._has_prev_score or self._prev_score < 2) and score >= 2
crossed_down = (not self._has_prev_score or self._prev_score > -2) and score <= -2
if crossed_up and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif crossed_down and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
self._prev_score = score
self._has_prev_score = True
def CreateClone(self):
return universal_signal_demo_strategy()