RSI Trader戦略
概要
この戦略は、MetaTraderのエキスパートアドバイザー*「RSI trader v0.15」*をStockSharp高レベルAPIで再現します。価格アクションと平滑化された相対力指数(RSI)の間でトレンド方向を整合させます。取引はデフォルトで1時間足を使用した単一の銘柄で行われますが、時間軸はCandleTypeパラメーターを通じて設定可能です。
取引ロジック
- 設定可能な期間で標準RSIを計算します。
- RSIを2つの単純移動平均(SMA)で平滑化します:速いシグナル平均と遅い確認平均。
- 終値の2つの移動平均を追跡します:短い単純移動平均と長い加重移動平均(元のMQL SMA/LWMAペアに近似)。
- 完成したローソク足ごとにトレンド状態を生成します:
- 強気アライメント:短い価格SMAが長い価格SMAを上回るかつ速いRSI SMAが遅いRSI SMAを上回る。
- 弱気アライメント:短い価格SMAが長い価格SMAを下回るかつ速いRSI SMAが遅いRSI SMAを下回る。
- 横ばい/不一致:移動平均が逆方向を示しており、明確なトレンドがないことを示す。
- 検出された状態に基づき行動します:
- 強気アライメントが現れ、現在ポジションが開いていない場合にロングポジションを開きます。
- 弱気アライメントが現れ、現在ポジションが開いていない場合にショートポジションを開きます。
- 横ばい状態が検出されたとき、開いているポジションを即座に閉じます(MQLバージョンの保護的終了を反映)。
- オプションのリバーサルモードはすべてのエントリー方向を反転させ、ユーザーが検出されたシグナルに対して逆張りで取引できるようにします。
この戦略はStockSharpの組み込み保護処理を尊重し、アクションを取る前に完成したローソク足を必要とします。
パラメーター
| パラメーター | 説明 | デフォルト値 |
|---|---|---|
RsiPeriod |
RSI計算に使用するルックバック期間。 | 14 |
ShortRsiMaPeriod |
RSI値に適用される速いSMAの長さ。 | 9 |
LongRsiMaPeriod |
RSI値に適用される遅いSMAの長さ。 | 45 |
ShortPriceMaPeriod |
終値に適用される短いSMAの長さ。 | 9 |
LongPriceMaPeriod |
価格に適用される長い加重移動平均の長さ。 | 45 |
Reverse |
trueの場合、買いと売りの注文が入れ替わります(元の「Reverse」入力を反映)。 |
false |
CandleType |
価格ローソク足のデータタイプ。デフォルトは1時間時間軸。 | 1h |
すべての整数パラメーターは、MetaTraderエキスパートの入力設定の柔軟性を反映した最適化範囲を公開します。
リスク管理
- 価格とRSIのトレンドが一致しなくなると即座にポジションが閉じられ(横ばい状態)、EAの即時終了動作を再現します。
StartProtection()は起動時に有効化され、StockSharpの保護インフラストラクチャと連携します。
メモ
- 戦略は取引サイズを定義するために
Strategyの基本Volumeプロパティに依存します。 - 完成したローソク足のみが処理されます。部分的な更新は早期シグナルを避けるために無視されます。
- 加重移動平均は、価格終値に適用された元の長いLWMAに一致させるために使用されます。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Candles;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// RSI trader strategy aligning price and RSI moving-average trends.
/// </summary>
public class RsiTraderStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _shortRsiMaPeriod;
private readonly StrategyParam<int> _longRsiMaPeriod;
private readonly StrategyParam<int> _shortPriceMaPeriod;
private readonly StrategyParam<int> _longPriceMaPeriod;
private readonly StrategyParam<bool> _reverse;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _closes = new();
private readonly List<decimal> _rsiValues = new();
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int ShortRsiMaPeriod { get => _shortRsiMaPeriod.Value; set => _shortRsiMaPeriod.Value = value; }
public int LongRsiMaPeriod { get => _longRsiMaPeriod.Value; set => _longRsiMaPeriod.Value = value; }
public int ShortPriceMaPeriod { get => _shortPriceMaPeriod.Value; set => _shortPriceMaPeriod.Value = value; }
public int LongPriceMaPeriod { get => _longPriceMaPeriod.Value; set => _longPriceMaPeriod.Value = value; }
public bool Reverse { get => _reverse.Value; set => _reverse.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public RsiTraderStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetDisplay("RSI Period", "RSI calculation length", "RSI").SetGreaterThanZero();
_shortRsiMaPeriod = Param(nameof(ShortRsiMaPeriod), 12).SetDisplay("Short RSI MA", "Short moving average on RSI", "RSI").SetGreaterThanZero();
_longRsiMaPeriod = Param(nameof(LongRsiMaPeriod), 60).SetDisplay("Long RSI MA", "Long moving average on RSI", "RSI").SetGreaterThanZero();
_shortPriceMaPeriod = Param(nameof(ShortPriceMaPeriod), 12).SetDisplay("Short Price MA", "Short simple moving average", "Price").SetGreaterThanZero();
_longPriceMaPeriod = Param(nameof(LongPriceMaPeriod), 60).SetDisplay("Long Price MA", "Long weighted moving average", "Price").SetGreaterThanZero();
_reverse = Param(nameof(Reverse), false).SetDisplay("Reverse", "Flip buy/sell signals", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Primary candle type", "Data");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closes.Clear();
_rsiValues.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_closes.Add(candle.ClosePrice);
var maxCache = Math.Max(LongPriceMaPeriod, Math.Max(LongRsiMaPeriod + RsiPeriod, 300));
if (_closes.Count > maxCache)
_closes.RemoveAt(0);
var rsi = CalculateRsi();
if (rsi is null)
return;
_rsiValues.Add(rsi.Value);
if (_rsiValues.Count > maxCache)
_rsiValues.RemoveAt(0);
if (_rsiValues.Count < LongRsiMaPeriod || _closes.Count < LongPriceMaPeriod)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var shortRsi = AverageLast(_rsiValues, ShortRsiMaPeriod);
var longRsi = AverageLast(_rsiValues, LongRsiMaPeriod);
var shortPrice = AverageLast(_closes, ShortPriceMaPeriod);
var longPrice = WeightedAverageLast(_closes, LongPriceMaPeriod);
var goLong = shortPrice > longPrice && shortRsi > longRsi;
var goShort = shortPrice < longPrice && shortRsi < longRsi;
var sideways = !goLong && !goShort;
if (sideways && Position != 0)
{
if (Position > 0)
SellMarket(Position);
else
BuyMarket(Math.Abs(Position));
return;
}
if (Position != 0)
return;
if (goLong)
{
if (Reverse)
SellMarket();
else
BuyMarket();
}
else if (goShort)
{
if (Reverse)
BuyMarket();
else
SellMarket();
}
}
private decimal? CalculateRsi()
{
if (_closes.Count <= RsiPeriod)
return null;
decimal gainSum = 0m;
decimal lossSum = 0m;
var start = _closes.Count - RsiPeriod;
for (var i = start; i < _closes.Count; i++)
{
var change = _closes[i] - _closes[i - 1];
if (change > 0m)
gainSum += change;
else
lossSum -= change;
}
var averageGain = gainSum / RsiPeriod;
var averageLoss = lossSum / RsiPeriod;
if (averageLoss == 0m)
return 100m;
var rs = averageGain / averageLoss;
return 100m - 100m / (1m + rs);
}
private static decimal AverageLast(IReadOnlyList<decimal> values, int length)
{
decimal sum = 0m;
var start = values.Count - length;
for (var i = start; i < values.Count; i++)
sum += values[i];
return sum / length;
}
private static decimal WeightedAverageLast(IReadOnlyList<decimal> values, int length)
{
decimal weightedSum = 0m;
decimal weightSum = 0m;
var start = values.Count - length;
var weight = 1m;
for (var i = start; i < values.Count; i++)
{
weightedSum += values[i] * weight;
weightSum += weight;
weight += 1m;
}
return weightSum > 0m ? weightedSum / weightSum : values[^1];
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class rsi_trader_strategy(Strategy):
def __init__(self):
super(rsi_trader_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14)
self._short_rsi_ma_period = self.Param("ShortRsiMaPeriod", 12)
self._long_rsi_ma_period = self.Param("LongRsiMaPeriod", 60)
self._short_price_ma_period = self.Param("ShortPriceMaPeriod", 12)
self._long_price_ma_period = self.Param("LongPriceMaPeriod", 60)
self._reverse = self.Param("Reverse", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._closes = []
self._rsi_values = []
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def ShortRsiMaPeriod(self):
return self._short_rsi_ma_period.Value
@ShortRsiMaPeriod.setter
def ShortRsiMaPeriod(self, value):
self._short_rsi_ma_period.Value = value
@property
def LongRsiMaPeriod(self):
return self._long_rsi_ma_period.Value
@LongRsiMaPeriod.setter
def LongRsiMaPeriod(self, value):
self._long_rsi_ma_period.Value = value
@property
def ShortPriceMaPeriod(self):
return self._short_price_ma_period.Value
@ShortPriceMaPeriod.setter
def ShortPriceMaPeriod(self, value):
self._short_price_ma_period.Value = value
@property
def LongPriceMaPeriod(self):
return self._long_price_ma_period.Value
@LongPriceMaPeriod.setter
def LongPriceMaPeriod(self, value):
self._long_price_ma_period.Value = value
@property
def Reverse(self):
return self._reverse.Value
@Reverse.setter
def Reverse(self, value):
self._reverse.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(rsi_trader_strategy, self).OnStarted2(time)
self._closes = []
self._rsi_values = []
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
self._closes.append(close)
max_cache = max(int(self.LongPriceMaPeriod), max(int(self.LongRsiMaPeriod) + int(self.RsiPeriod), 300))
while len(self._closes) > max_cache:
self._closes.pop(0)
rsi = self._calculate_rsi()
if rsi is None:
return
self._rsi_values.append(rsi)
while len(self._rsi_values) > max_cache:
self._rsi_values.pop(0)
if len(self._rsi_values) < int(self.LongRsiMaPeriod) or len(self._closes) < int(self.LongPriceMaPeriod):
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
short_rsi = self._average_last(self._rsi_values, int(self.ShortRsiMaPeriod))
long_rsi = self._average_last(self._rsi_values, int(self.LongRsiMaPeriod))
short_price = self._average_last(self._closes, int(self.ShortPriceMaPeriod))
long_price = self._weighted_average_last(self._closes, int(self.LongPriceMaPeriod))
go_long = short_price > long_price and short_rsi > long_rsi
go_short = short_price < long_price and short_rsi < long_rsi
sideways = not go_long and not go_short
if sideways and self.Position != 0:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
return
if self.Position != 0:
return
if go_long:
if self.Reverse:
self.SellMarket()
else:
self.BuyMarket()
elif go_short:
if self.Reverse:
self.BuyMarket()
else:
self.SellMarket()
def _calculate_rsi(self):
rsi_period = int(self.RsiPeriod)
if len(self._closes) <= rsi_period:
return None
gain_sum = 0.0
loss_sum = 0.0
start = len(self._closes) - rsi_period
for i in range(start, len(self._closes)):
change = self._closes[i] - self._closes[i - 1]
if change > 0.0:
gain_sum += change
else:
loss_sum -= change
average_gain = gain_sum / rsi_period
average_loss = loss_sum / rsi_period
if average_loss == 0.0:
return 100.0
rs = average_gain / average_loss
return 100.0 - 100.0 / (1.0 + rs)
def _average_last(self, values, length):
total = 0.0
start = len(values) - length
for i in range(start, len(values)):
total += values[i]
return total / length
def _weighted_average_last(self, values, length):
weighted_sum = 0.0
weight_sum = 0.0
start = len(values) - length
weight = 1.0
for i in range(start, len(values)):
weighted_sum += values[i] * weight
weight_sum += weight
weight += 1.0
if weight_sum > 0.0:
return weighted_sum / weight_sum
return values[-1]
def OnReseted(self):
super(rsi_trader_strategy, self).OnReseted()
self._closes = []
self._rsi_values = []
def CreateClone(self):
return rsi_trader_strategy()