不安定な市場におけるグリッド取引
概要
この戦略は、StockSharp の高レベルの API を使用して、MetaTrader エキスパート「Gridtrading_at_volatile_market.mq4」を複製します。取引時間枠でパターンを飲み込むエントリーを確認しながら、より高い時間枠で検出された Donchian チャネル境界を中心に取引します。グリッドがアクティブになると、この戦略は、価格がより高い時間枠 ATR の倍数に達したときに平均注文を追加し、ポートフォリオの利益またはドローダウンの目標に達したときに終了します。
仕組み
- ユーザーが選択した取引タイムフレームと、そこから自動的に導出されるより高いタイムフレーム (M1→M5→M15→M30→H1→H4→D1) の 2 つのローソク足ストリームが使用されます。
- より高い時間枠では、戦略は次のように計算します。
ATR(20)はグリッド間隔のサイズを指定します。SMA(SlowMaLength)は、RSI と一緒にトレンドをフィルタリングします。DonchianChannels(20)はサポートレベルとレジスタンスレベルです。
- 取引時間枠上で、最後の 2 つの完了したローソク足を追跡して、強気または弱気の巻き込みパターンを検出します。
- 長いグリッドは、前のローソク足が Donchian の下側バンドに触れ、強気の巻き込みパターンを形成し、RSI が売られ過ぎの状態を確認したときに始まります (価格が高い時間枠 SMA を上回っている間、
RSI < 35)。短いグリッドは、RSI > 65の上部バンドでこれらのルールを反映します。 - 最初の成行注文の後、この戦略はアンカーとして初値を維持します。現在のグリッド ステップで価格がポジションに対して
2 * ATRだけ移動すると、出来高にGridMultiplierを掛けた別の注文が追加されます。 - 次のいずれかの場合、グリッドは閉じられ、すべての注文がキャンセルされます。
- (実現+未実現) 合計損益は
TakeProfitFactor * total grid volumeを超えます。 - ドローダウンは
-MaxDrawdownFraction * initial portfolio valueを下回ります。
- (実現+未実現) 合計損益は
パラメーター
- TakeProfitFactor – グリッドを閉じるために必要な総グリッド量の利益の倍数 (デフォルトは
0.1)。 - SlowMaLength – フィルタリングに使用される上位タイムフレーム SMA の期間 (デフォルトは
50)。 - GridMultiplier – 追加の各平均次数に適用される幾何学的係数 (デフォルトは
1.5)。 - BaseOrderVolume – グリッド内の最初の注文のボリューム (デフォルトは
0.1)。 - MaxDrawdownFraction – グリッドが強制的に閉じられる前のポートフォリオの初期値に対する最大損失 (デフォルトは
0.8)。 - CandleType – 取引時間枠。より高い時間枠は自動的に推測されます。
注意事項
- 再塗装を避けるため、閉じたキャンドルのみが処理されます。
- この戦略は、オープン損益を評価するために利用可能な買値/売値に依存します。最新の取引価格のみが提供されている場合、近似値の精度が低くなる可能性があります。
- ポートフォリオ情報が利用できない場合、ドローダウン保護はスキップされ、利益目標が達成されるかポジションが手動でクローズされるまでグリッドを実行できます。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Grid Trading at Volatile Market" MetaTrader expert.
/// Uses RSI + SMA trend filter for initial entry then manages a simple
/// grid of averaging orders based on ATR distance.
/// </summary>
public class GridTradingAtVolatileMarketStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<int> _maxGridLevels;
private RelativeStrengthIndex _rsi;
private readonly Queue<decimal> _smaQueue = new();
private decimal _smaSum;
private Sides? _gridDirection;
private int _gridLevel;
private decimal _lastEntryPrice;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public int SmaPeriod
{
get => _smaPeriod.Value;
set => _smaPeriod.Value = value;
}
public int MaxGridLevels
{
get => _maxGridLevels.Value;
set => _maxGridLevels.Value = value;
}
public GridTradingAtVolatileMarketStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signal detection", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period for entry signals", "Indicators");
_smaPeriod = Param(nameof(SmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("SMA Period", "SMA period for trend filter", "Indicators");
_maxGridLevels = Param(nameof(MaxGridLevels), 3)
.SetGreaterThanZero()
.SetDisplay("Max Grid Levels", "Maximum averaging levels", "Grid");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_smaQueue.Clear();
_smaSum = 0;
_gridDirection = null;
_gridLevel = 0;
_lastEntryPrice = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
// Manual SMA
_smaQueue.Enqueue(close);
_smaSum += close;
while (_smaQueue.Count > SmaPeriod)
_smaSum -= _smaQueue.Dequeue();
if (!_rsi.IsFormed || _smaQueue.Count < SmaPeriod)
return;
var smaValue = _smaSum / _smaQueue.Count;
var volume = Volume;
if (volume <= 0)
volume = 1;
// If no grid active, look for entry signals
if (_gridDirection is null)
{
// Buy: RSI oversold + price below SMA (mean reversion)
if (rsiValue < 35 && close < smaValue)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(volume);
_gridDirection = Sides.Buy;
_gridLevel = 1;
_lastEntryPrice = close;
}
// Sell: RSI overbought + price above SMA
else if (rsiValue > 65 && close > smaValue)
{
if (Position > 0)
SellMarket(Position);
SellMarket(volume);
_gridDirection = Sides.Sell;
_gridLevel = 1;
_lastEntryPrice = close;
}
}
else
{
// Grid management - add levels on adverse moves
var distanceThreshold = _lastEntryPrice * 0.005m; // 0.5% grid step
if (_gridDirection == Sides.Buy)
{
// Price moved further down - add to grid
if (_gridLevel < MaxGridLevels && close < _lastEntryPrice - distanceThreshold)
{
BuyMarket(volume);
_gridLevel++;
_lastEntryPrice = close;
}
// Take profit - price recovered above SMA
else if (close > smaValue && rsiValue > 50)
{
if (Position > 0)
SellMarket(Position);
_gridDirection = null;
_gridLevel = 0;
}
}
else if (_gridDirection == Sides.Sell)
{
// Price moved further up - add to grid
if (_gridLevel < MaxGridLevels && close > _lastEntryPrice + distanceThreshold)
{
SellMarket(volume);
_gridLevel++;
_lastEntryPrice = close;
}
// Take profit - price fell below SMA
else if (close < smaValue && rsiValue < 50)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
_gridDirection = null;
_gridLevel = 0;
}
}
}
}
/// <inheritdoc />
protected override void OnReseted()
{
_rsi = null;
_smaQueue.Clear();
_smaSum = 0;
_gridDirection = null;
_gridLevel = 0;
_lastEntryPrice = 0;
base.OnReseted();
}
}
import clr
from collections import deque
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
from StockSharp.Algo.Strategies import Strategy
class grid_trading_at_volatile_market_strategy(Strategy):
"""
Grid Trading at Volatile Market: RSI + SMA trend filter for initial entry
then manages a grid of averaging orders based on distance threshold.
"""
def __init__(self):
super(grid_trading_at_volatile_market_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI period for entry signals", "Indicators")
self._sma_period = self.Param("SmaPeriod", 50) \
.SetDisplay("SMA Period", "SMA period for trend filter", "Indicators")
self._max_grid_levels = self.Param("MaxGridLevels", 3) \
.SetDisplay("Max Grid Levels", "Maximum averaging levels", "Grid")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe for signal detection", "General")
self._sma_queue = deque()
self._sma_sum = 0.0
self._grid_direction = None # 'buy' or 'sell'
self._grid_level = 0
self._last_entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(grid_trading_at_volatile_market_strategy, self).OnReseted()
self._sma_queue = deque()
self._sma_sum = 0.0
self._grid_direction = None
self._grid_level = 0
self._last_entry_price = 0.0
def OnStarted2(self, time):
super(grid_trading_at_volatile_market_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawOwnTrades(area)
def _process_candle(self, candle, rsi_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
rsi = float(rsi_val)
self._sma_queue.append(close)
self._sma_sum += close
sma_period = self._sma_period.Value
while len(self._sma_queue) > sma_period:
self._sma_sum -= self._sma_queue.popleft()
if len(self._sma_queue) < sma_period:
return
sma_value = self._sma_sum / len(self._sma_queue)
if self._grid_direction is None:
if rsi < 35 and close < sma_value:
self.BuyMarket()
self._grid_direction = 'buy'
self._grid_level = 1
self._last_entry_price = close
elif rsi > 65 and close > sma_value:
self.SellMarket()
self._grid_direction = 'sell'
self._grid_level = 1
self._last_entry_price = close
else:
distance_threshold = self._last_entry_price * 0.005
if self._grid_direction == 'buy':
if self._grid_level < self._max_grid_levels.Value and close < self._last_entry_price - distance_threshold:
self.BuyMarket()
self._grid_level += 1
self._last_entry_price = close
elif close > sma_value and rsi > 50:
if self.Position > 0:
self.SellMarket()
self._grid_direction = None
self._grid_level = 0
elif self._grid_direction == 'sell':
if self._grid_level < self._max_grid_levels.Value and close > self._last_entry_price + distance_threshold:
self.SellMarket()
self._grid_level += 1
self._last_entry_price = close
elif close < sma_value and rsi < 50:
if self.Position < 0:
self.BuyMarket()
self._grid_direction = None
self._grid_level = 0
def CreateClone(self):
return grid_trading_at_volatile_market_strategy()