Two MA RSI 戦略
概要
Two MA RSI 戦略は、オリジナルの MetaTrader エキスパートアドバイザー「2MA_RSI」を変換したものです。相対力指数(RSI)フィルターで確認された、高速と低速の指数移動平均(EMA)クロスオーバーを使用します。注文は、損失後に次の注文ボリュームを増やすマーチンゲールスタイルの資金管理ブロックでサイズ設定されます。StockSharp バージョンは完成したロウソク足のみで動作し、価格ポイントでのオリジナルのテイクプロフィットとストップロスの動作を再現します。
データとインジケーター
- 戦略は
CandleTypeで定義された単一のロウソク足シリーズをサブスクライブします(デフォルトで5分ロウソク足)。 - 各完成バーで3つのインジケーターが計算されます:
FastLengthEMA(ロウソク足の終値に適用)。SlowLengthEMA。RsiLengthの長さを持つ RSI。
- EMA クロスオーバーを検出するためにインジケーターバッファからデータを引き出さずに、過去のインジケーター値が内部に保存されます。
エントリーロジック
- イントラバーの再評価を避けるために、前のロウソク足が完成している必要があります。
- アクティブなポジションは許可されていません(
Position == 0)。 - ロングエントリー:
- 高速 EMA が低速 EMA を上回る(現在のバーの高速 EMA が低速 EMA より大きく、前のバーで高速 EMA < 低速 EMA だった)。
- RSI 値が
RsiOversoldを下回り、売られすぎの市場を確認する。
- ショートエントリー:
- 高速 EMA が類似の条件で低速 EMA を下回る(高速 EMA が現在低速 EMA より低く、以前は上にあった)。
- RSI が
RsiOverboughtを上回り、買われすぎの市場を示す。
- すべての条件が満たされると、戦略はマーチンゲールモジュールに従ってサイズ設定された成行注文を送信します。
決済ロジック
- 各エントリーの直後に保護的なストップロスとテイクプロフィットが計算されます。距離は「ポイント」で定義され、銘柄の
PriceStepを通して変換されます:- ロング:
- ストップロス =
エントリー価格 - StopLossPoints * PriceStep。 - テイクプロフィット =
エントリー価格 + TakeProfitPoints * PriceStep。
- ストップロス =
- ショート:
- ストップロス =
エントリー価格 + StopLossPoints * PriceStep。 - テイクプロフィット =
エントリー価格 - TakeProfitPoints * PriceStep。
- ストップロス =
- ロング:
- これらの保護レベルのみが取引を閉じます。戦略は次のロウソク足を待って安値/高値が目標またはストップに触れたかどうかを確認し、それに応じて
ClosePosition()成行注文を送信します。 - 決済の優先度はオリジナルロボットの保守的な動作と一致します:両方のレベルが同じロウソク足の範囲内に入る場合、ストップロスがテイクプロフィットより先に評価されます。
ポジションサイジングとマーチンゲール
- 基本ボリュームは各エントリーで
floor(balance / BalanceDivider) * VolumeStepとして計算されます。値は常に1ボリュームステップ以上を維持し、ポートフォリオのCurrentValueを使用します(必要な場合はBeginValueにフォールバック)。 - 各損失決済後、マーチンゲールステージは
MaxDoublingsまで1ずつ増加します。次の注文ボリュームは2^stageで掛け算されます。 - 勝ちトレードまたは最大倍数に達すると、ステージはゼロにリセットされ、基本ボリュームに戻ります。
MaxDoublingsがゼロまたは負の場合、サイズは増加せず基本ボリュームに等しくなります。
追加の動作
- 戦略は前の EMA 値を内部で追跡し、インジケーターバッファから過去の値を要求しません。
- 注文は、戦略がオンラインで、インジケーターが形成されており、取引が許可されている場合のみ実行されます。
- チャート出力は価格ロウソク足、自分の取引、および視覚的分析のための3つのインジケーターを描画します。
パラメーター
| パラメーター | 説明 | デフォルト |
|---|---|---|
FastLength |
高速 EMA の長さ。 | 5 |
SlowLength |
低速 EMA の長さ。 | 20 |
RsiLength |
RSI 計算で使用されるバーの数。 | 14 |
RsiOverbought |
新規ロングをブロックしショートを許可する RSI レベル。 | 70 |
RsiOversold |
ロングを許可する RSI レベル。 | 30 |
StopLossPoints |
価格ステップで表されたストップロス距離。 | 500 |
TakeProfitPoints |
価格ステップでのテイクプロフィット距離。 | 1500 |
BalanceDivider |
基本注文サイズを取得するためにポートフォリオ値を除算。 | 1000 |
MaxDoublings |
連続損失後のマーチンゲール倍数の最大数。 | 1 |
CandleType |
戦略が使用するロウソク足シリーズ。 | 5分時間軸 |
使用上の注意
- 点ベースのリスク管理とポジションサイジングが一貫して機能するように、有効な
PriceStepとVolumeStepメタデータを持つポートフォリオと銘柄を提供してください。 - 決済に成行注文が使用されるため、MetaTrader バージョンの指値注文と比較してスリッページとスプレッドが発生する可能性がありますが、ストップ/テイクの評価ロジックは保持されます。
- 戦略は Python バージョンを作成しません;要求に応じて C# 実装のみが提供されます。
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 crossover strategy with RSI confirmation and martingale sizing.
/// </summary>
public class TwoMaRsiStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _balanceDivider;
private readonly StrategyParam<int> _maxDoublings;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private RelativeStrengthIndex _rsi;
private decimal? _previousFast;
private decimal? _previousSlow;
private decimal _entryPrice;
private decimal _stopPrice;
private decimal _takeProfitPrice;
private int _martingaleStage;
private bool _isClosing;
/// <summary>
/// Initializes a new instance of the <see cref="TwoMaRsiStrategy"/> class.
/// </summary>
public TwoMaRsiStrategy()
{
_fastLength = Param(nameof(FastLength), 5)
.SetDisplay("Fast EMA Length", "Length of the fast exponential moving average", "Indicators")
.SetOptimize(2, 20, 1);
_slowLength = Param(nameof(SlowLength), 20)
.SetDisplay("Slow EMA Length", "Length of the slow exponential moving average", "Indicators")
.SetOptimize(10, 60, 5);
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "Number of bars for the RSI calculation", "Indicators")
.SetOptimize(5, 30, 1);
_rsiOverbought = Param(nameof(RsiOverbought), 50m)
.SetDisplay("RSI Overbought", "Upper RSI threshold for short entries", "Signals");
_rsiOversold = Param(nameof(RsiOversold), 50m)
.SetDisplay("RSI Oversold", "Lower RSI threshold for long entries", "Signals");
_stopLossPoints = Param(nameof(StopLossPoints), 500m)
.SetDisplay("Stop Loss (points)", "Stop loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 1500m)
.SetDisplay("Take Profit (points)", "Take profit distance in price steps", "Risk");
_balanceDivider = Param(nameof(BalanceDivider), 1000m)
.SetDisplay("Balance Divider", "Divides portfolio value to estimate base order volume", "Money Management");
_maxDoublings = Param(nameof(MaxDoublings), 1)
.SetDisplay("Max Doublings", "Maximum number of martingale doublings", "Money Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series for the strategy", "General");
}
/// <summary>
/// Fast EMA length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow EMA length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Overbought threshold for RSI.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
/// <summary>
/// Oversold threshold for RSI.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Divider applied to the portfolio value to calculate the base order volume.
/// </summary>
public decimal BalanceDivider
{
get => _balanceDivider.Value;
set => _balanceDivider.Value = value;
}
/// <summary>
/// Maximum number of martingale doublings.
/// </summary>
public int MaxDoublings
{
get => _maxDoublings.Value;
set => _maxDoublings.Value = value;
}
/// <summary>
/// Candle data type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = null;
_slowEma = null;
_rsi = null;
_previousFast = null;
_previousSlow = null;
_entryPrice = default;
_stopPrice = default;
_takeProfitPrice = default;
_martingaleStage = 0;
_isClosing = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new ExponentialMovingAverage
{
Length = FastLength
};
_slowEma = new ExponentialMovingAverage
{
Length = SlowLength
};
_rsi = new RelativeStrengthIndex
{
Length = RsiLength
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (Position == 0 && _isClosing)
{
_isClosing = false;
_entryPrice = default;
_stopPrice = default;
_takeProfitPrice = default;
}
var fastResult = _fastEma.Process(candle);
var slowResult = _slowEma.Process(candle);
var rsiResult = _rsi.Process(candle);
if (fastResult.IsEmpty || slowResult.IsEmpty || rsiResult.IsEmpty)
{
return;
}
if (!_fastEma.IsFormed || !_slowEma.IsFormed || !_rsi.IsFormed)
{
_previousFast = fastResult.GetValue<decimal>();
_previousSlow = slowResult.GetValue<decimal>();
return;
}
var fast = fastResult.GetValue<decimal>();
var slow = slowResult.GetValue<decimal>();
var rsi = rsiResult.GetValue<decimal>();
var point = GetPoint();
if (Position > 0)
{
var stopHit = candle.LowPrice <= _stopPrice;
var takeHit = candle.HighPrice >= _takeProfitPrice;
if (!_isClosing && stopHit)
{
_isClosing = true;
ClosePosition();
RegisterLoss();
}
else if (!_isClosing && takeHit)
{
_isClosing = true;
ClosePosition();
RegisterWin();
}
}
else if (Position < 0)
{
var stopHit = candle.HighPrice >= _stopPrice;
var takeHit = candle.LowPrice <= _takeProfitPrice;
if (!_isClosing && stopHit)
{
_isClosing = true;
ClosePosition();
RegisterLoss();
}
else if (!_isClosing && takeHit)
{
_isClosing = true;
ClosePosition();
RegisterWin();
}
}
else if (!_isClosing)
{
if (_previousFast is null || _previousSlow is null)
{
_previousFast = fast;
_previousSlow = slow;
return;
}
var prevFast = _previousFast.Value;
var prevSlow = _previousSlow.Value;
var crossUp = prevFast < prevSlow && fast > slow && rsi < RsiOversold;
var crossDown = prevFast > prevSlow && fast < slow && rsi > RsiOverbought;
if (crossUp)
{
var volume = CalculateOrderVolume();
if (volume > 0m)
{
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice - StopLossPoints * point;
_takeProfitPrice = _entryPrice + TakeProfitPoints * point;
}
}
else if (crossDown)
{
var volume = CalculateOrderVolume();
if (volume > 0m)
{
SellMarket(volume);
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice + StopLossPoints * point;
_takeProfitPrice = _entryPrice - TakeProfitPoints * point;
}
}
}
_previousFast = fast;
_previousSlow = slow;
}
private decimal GetPoint()
{
var step = Security?.PriceStep ?? 1m;
return step > 0m ? step : 1m;
}
private decimal CalculateOrderVolume()
{
var step = Security?.VolumeStep ?? 1m;
if (step <= 0m)
step = 1m;
var baseVolume = step;
var divider = BalanceDivider;
var balance = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
if (divider > 0m && balance > 0m)
{
var count = Math.Floor((double)(balance / divider));
baseVolume = (decimal)count * step;
if (baseVolume < step)
baseVolume = step;
}
var multiplier = CalculateMartingaleMultiplier();
var volume = baseVolume * multiplier;
if (volume < step)
volume = step;
var ratio = volume / step;
volume = Math.Ceiling(ratio) * step;
return volume;
}
private decimal CalculateMartingaleMultiplier()
{
if (MaxDoublings <= 0 || _martingaleStage <= 0)
return 1m;
var stage = Math.Min(_martingaleStage, MaxDoublings);
return (decimal)Math.Pow(2d, stage);
}
private void RegisterWin()
{
_martingaleStage = 0;
}
private void ClosePosition()
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
}
private void RegisterLoss()
{
if (MaxDoublings <= 0)
{
_martingaleStage = 0;
return;
}
if (_martingaleStage < MaxDoublings)
{
_martingaleStage++;
}
else
{
_martingaleStage = 0;
}
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class two_ma_rsi_strategy(Strategy):
def __init__(self):
super(two_ma_rsi_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 5)
self._slow_length = self.Param("SlowLength", 20)
self._rsi_length = self.Param("RsiLength", 14)
self._rsi_overbought = self.Param("RsiOverbought", 50.0)
self._rsi_oversold = self.Param("RsiOversold", 50.0)
self._stop_loss_points = self.Param("StopLossPoints", 500.0)
self._take_profit_points = self.Param("TakeProfitPoints", 1500.0)
self._balance_divider = self.Param("BalanceDivider", 1000.0)
self._max_doublings = self.Param("MaxDoublings", 1)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._fast_ema = None
self._slow_ema = None
self._rsi = None
self._previous_fast = None
self._previous_slow = None
self._entry_price = 0.0
self._stop_price = 0.0
self._take_profit_price = 0.0
self._martingale_stage = 0
self._is_closing = False
@property
def FastLength(self):
return self._fast_length.Value
@property
def SlowLength(self):
return self._slow_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def RsiOverbought(self):
return self._rsi_overbought.Value
@property
def RsiOversold(self):
return self._rsi_oversold.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def BalanceDivider(self):
return self._balance_divider.Value
@property
def MaxDoublings(self):
return self._max_doublings.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(two_ma_rsi_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
pos = float(self.Position)
if pos == 0 and self._is_closing:
self._is_closing = False
self._entry_price = 0.0
self._stop_price = 0.0
self._take_profit_price = 0.0
civ1 = CandleIndicatorValue(self._fast_ema, candle)
civ1.IsFinal = True
fast_result = self._fast_ema.Process(civ1)
civ2 = CandleIndicatorValue(self._slow_ema, candle)
civ2.IsFinal = True
slow_result = self._slow_ema.Process(civ2)
civ3 = CandleIndicatorValue(self._rsi, candle)
civ3.IsFinal = True
rsi_result = self._rsi.Process(civ3)
if fast_result.IsEmpty or slow_result.IsEmpty or rsi_result.IsEmpty:
return
if not self._fast_ema.IsFormed or not self._slow_ema.IsFormed or not self._rsi.IsFormed:
try:
self._previous_fast = float(fast_result.Value)
self._previous_slow = float(slow_result.Value)
except:
pass
return
try:
fast = float(fast_result.Value)
slow = float(slow_result.Value)
rsi = float(rsi_result.Value)
except:
return
point = self._get_point()
pos = float(self.Position)
if pos > 0:
stop_hit = float(candle.LowPrice) <= self._stop_price
take_hit = float(candle.HighPrice) >= self._take_profit_price
if not self._is_closing and stop_hit:
self._is_closing = True
self._close_position()
self._register_loss()
elif not self._is_closing and take_hit:
self._is_closing = True
self._close_position()
self._register_win()
elif pos < 0:
stop_hit = float(candle.HighPrice) >= self._stop_price
take_hit = float(candle.LowPrice) <= self._take_profit_price
if not self._is_closing and stop_hit:
self._is_closing = True
self._close_position()
self._register_loss()
elif not self._is_closing and take_hit:
self._is_closing = True
self._close_position()
self._register_win()
elif not self._is_closing:
if self._previous_fast is None or self._previous_slow is None:
self._previous_fast = fast
self._previous_slow = slow
return
prev_fast = self._previous_fast
prev_slow = self._previous_slow
cross_up = prev_fast < prev_slow and fast > slow and rsi < float(self.RsiOversold)
cross_down = prev_fast > prev_slow and fast < slow and rsi > float(self.RsiOverbought)
if cross_up:
volume = self._calculate_order_volume()
if volume > 0:
self.BuyMarket(volume)
self._entry_price = float(candle.ClosePrice)
self._stop_price = self._entry_price - float(self.StopLossPoints) * point
self._take_profit_price = self._entry_price + float(self.TakeProfitPoints) * point
elif cross_down:
volume = self._calculate_order_volume()
if volume > 0:
self.SellMarket(volume)
self._entry_price = float(candle.ClosePrice)
self._stop_price = self._entry_price + float(self.StopLossPoints) * point
self._take_profit_price = self._entry_price - float(self.TakeProfitPoints) * point
self._previous_fast = fast
self._previous_slow = slow
def _get_point(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
return step if step > 0 else 1.0
def _calculate_order_volume(self):
sec = self.Security
step = float(sec.VolumeStep) if sec is not None and sec.VolumeStep is not None else 1.0
if step <= 0:
step = 1.0
base_volume = step
divider = float(self.BalanceDivider)
portfolio = self.Portfolio
balance = 0.0
if portfolio is not None:
if portfolio.CurrentValue is not None:
balance = float(portfolio.CurrentValue)
elif portfolio.BeginValue is not None:
balance = float(portfolio.BeginValue)
if divider > 0 and balance > 0:
import math
count = math.floor(balance / divider)
base_volume = count * step
if base_volume < step:
base_volume = step
multiplier = self._calculate_martingale_multiplier()
volume = base_volume * multiplier
if volume < step:
volume = step
import math
ratio = volume / step
volume = math.ceil(ratio) * step
return volume
def _calculate_martingale_multiplier(self):
if self.MaxDoublings <= 0 or self._martingale_stage <= 0:
return 1.0
stage = min(self._martingale_stage, self.MaxDoublings)
return 2.0 ** stage
def _register_win(self):
self._martingale_stage = 0
def _register_loss(self):
if self.MaxDoublings <= 0:
self._martingale_stage = 0
return
if self._martingale_stage < self.MaxDoublings:
self._martingale_stage += 1
else:
self._martingale_stage = 0
def _close_position(self):
pos = float(self.Position)
if pos > 0:
self.SellMarket(pos)
elif pos < 0:
self.BuyMarket(abs(pos))
def OnReseted(self):
super(two_ma_rsi_strategy, self).OnReseted()
self._fast_ema = None
self._slow_ema = None
self._rsi = None
self._previous_fast = None
self._previous_slow = None
self._entry_price = 0.0
self._stop_price = 0.0
self._take_profit_price = 0.0
self._martingale_stage = 0
self._is_closing = False
def CreateClone(self):
return two_ma_rsi_strategy()