Grid Bot戦略
グリッドボットは、事前に定義された価格レンジを等間隔のレベルに分割し、それらの間の振動を取引します。価格がグリッドの下半分に向かって移動するとき、戦略はロングポジションを積み上げ、価格が上半分に戻ったときに売却します。このアプローチは、明確な境界を持つ横ばい相場で機能します。
方向性バイアスは仮定されていません。ボットはただグリッドラインへの近接に反応します。
詳細
- エントリー条件:
- ロング: ロングポジションなしの状態で価格が下半分のレベルに触れる
- ショート: ショートポジションなしの状態で価格が上半分のレベルに触れる
- ロング/ショート: 両方
- エグジット条件:
- 逆のエントリーシグナルが既存ポジションを決済する
- ストップ: なし
- デフォルト値:
UpperLimit= 48000LowerLimit= 45000GridCount= 10
- フィルター:
- カテゴリ: Range trading
- 方向: 両方
- インジケーター: Price levels
- ストップ: いいえ
- 複雑さ: 低
- 時間軸: 短期
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Grid Bot Strategy.
/// Splits the predefined price range between <see cref="LowerLimit"/> and <see cref="UpperLimit"/>
/// into <see cref="GridCount"/> equal levels and trades the oscillations between them.
/// Touching a level in the lower half opens a long, touching a level in the upper half opens a short,
/// and every signal closes the opposite position first.
/// </summary>
public class GridBotStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<decimal> _upperLimit;
private readonly StrategyParam<decimal> _lowerLimit;
private readonly StrategyParam<int> _gridCount;
// Grid line the previous candle closed on, -1 before the first one is evaluated.
private int _prevLevel;
/// <summary>
/// Initializes a new instance of the <see cref="GridBotStrategy"/>.
/// </summary>
public GridBotStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_upperLimit = Param(nameof(UpperLimit), 48000m)
.SetGreaterThanZero()
.SetDisplay("Upper Limit", "Top price of the grid range", "Grid Settings");
_lowerLimit = Param(nameof(LowerLimit), 45000m)
.SetGreaterThanZero()
.SetDisplay("Lower Limit", "Bottom price of the grid range", "Grid Settings");
_gridCount = Param(nameof(GridCount), 10)
.SetGreaterThanZero()
.SetDisplay("Grid Count", "Number of equal levels the range is split into", "Grid Settings");
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
/// <summary>
/// Top price of the grid range.
/// </summary>
public decimal UpperLimit
{
get => _upperLimit.Value;
set => _upperLimit.Value = value;
}
/// <summary>
/// Bottom price of the grid range.
/// </summary>
public decimal LowerLimit
{
get => _lowerLimit.Value;
set => _lowerLimit.Value = value;
}
/// <summary>
/// Number of equal levels the range is split into.
/// </summary>
public int GridCount
{
get => _gridCount.Value;
set => _gridCount.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevLevel = -1;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (UpperLimit <= LowerLimit)
throw new InvalidOperationException($"{nameof(UpperLimit)} must be above {nameof(LowerLimit)}.");
_prevLevel = -1;
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 (!IsFormedAndOnlineAndAllowTrading())
return;
var level = GetLevel(candle.ClosePrice);
// A touch is the move onto another line; standing on the same one is not a new signal.
if (level == _prevLevel)
return;
_prevLevel = level;
// The middle line splits the range into halves and carries no bias of its own.
var middle = GridCount / 2m;
if (level < middle && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (level > middle && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
// Index of the grid line the price sits on, counted from LowerLimit up to GridCount.
private int GetLevel(decimal price)
{
var step = (UpperLimit - LowerLimit) / GridCount;
// A price outside the predefined range belongs to the outermost line of the grid.
var clamped = Math.Clamp(price, LowerLimit, UpperLimit);
return (int)Math.Floor((clamped - LowerLimit) / step + 0.5m);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class grid_bot_strategy(Strategy):
"""Grid Bot Strategy. Fixed grid of equal levels between LowerLimit and UpperLimit."""
def __init__(self):
super(grid_bot_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._upper_limit = self.Param("UpperLimit", 48000.0) \
.SetDisplay("Upper Limit", "Top price of the grid range", "Grid Settings")
self._lower_limit = self.Param("LowerLimit", 45000.0) \
.SetDisplay("Lower Limit", "Bottom price of the grid range", "Grid Settings")
self._grid_count = self.Param("GridCount", 10) \
.SetDisplay("Grid Count", "Number of equal levels the range is split into", "Grid Settings")
# Grid line the previous candle closed on, -1 before the first one is evaluated.
self._prev_level = -1
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(grid_bot_strategy, self).OnReseted()
self._prev_level = -1
def OnStarted2(self, time):
super(grid_bot_strategy, self).OnStarted2(time)
if float(self._upper_limit.Value) <= float(self._lower_limit.Value):
raise Exception("UpperLimit must be above LowerLimit.")
self._prev_level = -1
subscription = self.SubscribeCandles(self.candle_type)
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
if not self.IsFormedAndOnlineAndAllowTrading():
return
level = self._get_level(float(candle.ClosePrice))
# A touch is the move onto another line; standing on the same one is not a new signal.
if level == self._prev_level:
return
self._prev_level = level
# The middle line splits the range into halves and carries no bias of its own.
middle = int(self._grid_count.Value) / 2.0
if level < middle and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif level > middle and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
def _get_level(self, price):
"""Index of the grid line the price sits on, counted from LowerLimit up to GridCount."""
upper = float(self._upper_limit.Value)
lower = float(self._lower_limit.Value)
count = int(self._grid_count.Value)
step = (upper - lower) / count
# A price outside the predefined range belongs to the outermost line of the grid.
clamped = min(max(price, lower), upper)
return int(Math.Floor((clamped - lower) / step + 0.5))
def CreateClone(self):
return grid_bot_strategy()