Grid Bot-Strategie
Der Grid Bot teilt einen vordefinierten Preisbereich in gleiche Ebenen auf und handelt die Schwingungen zwischen ihnen. Wenn der Preis zur unteren Hälfte des Gitters driftet, akkumuliert die Strategie Long-Positionen und verkauft sie, wenn der Preis zur oberen Hälfte zurückkehrt. Dieser Ansatz gedeiht in Seitwärtsmärkten mit klaren Grenzen.
Es wird keine Richtungsverzerrung angenommen; der Bot reagiert einfach auf die Nähe zu den Gitterlinien.
Details
- Einstiegskriterien:
- Long: Preis berührt ein Niveau in der unteren Hälfte ohne Long-Position
- Short: Preis berührt ein Niveau in der oberen Hälfte ohne Short-Position
- Long/Short: Beide
- Ausstiegskriterien:
- Entgegengesetztes Einstiegssignal schließt bestehende Position
- Stops: Keine
- Standardwerte:
UpperLimit= 74000LowerLimit= 60000GridCount= 10
- Filter:
- Kategorie: Range trading
- Richtung: Beide
- Indikatoren: Price levels
- Stops: Nein
- Komplexität: Niedrig
- Zeitrahmen: Kurzfristig
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
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;
// Most recently touched grid line; -1 when the candle did not touch any line.
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), 74000m)
.SetGreaterThanZero()
.SetDisplay("Upper Limit", "Top price of the grid range", "Grid Settings");
_lowerLimit = Param(nameof(LowerLimit), 60000m)
.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 = GetTouchedLevel(candle);
// A gap entirely outside the grid is not a touch of its outermost line.
if (level < 0)
{
_prevLevel = -1;
return;
}
// Repeated touches of the same line do not create duplicate signals.
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));
}
// Use only lines the candle actually traded through. If several were touched, use the
// one nearest the close, breaking equal-distance ties toward the lower line.
private int GetTouchedLevel(ICandleMessage candle)
{
var step = (UpperLimit - LowerLimit) / GridCount;
var touched = -1;
var distance = decimal.MaxValue;
for (var index = 0; index <= GridCount; index++)
{
var price = LowerLimit + index * step;
if (price < candle.LowPrice || price > candle.HighPrice)
continue;
var candidateDistance = Math.Abs(price - candle.ClosePrice);
if (candidateDistance >= distance)
continue;
distance = candidateDistance;
touched = index;
}
return touched;
}
}
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", 74000.0) \
.SetDisplay("Upper Limit", "Top price of the grid range", "Grid Settings")
self._lower_limit = self.Param("LowerLimit", 60000.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")
# Most recently touched line, or -1 if no line was touched.
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_touched_level(candle)
# Never clamp an out-of-range candle to an untouched outer line.
if level < 0:
self._prev_level = -1
return
# 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_touched_level(self, candle):
"""Among lines actually touched, select the nearest to the close; prefer the lower tie."""
upper = float(self._upper_limit.Value)
lower = float(self._lower_limit.Value)
count = int(self._grid_count.Value)
step = (upper - lower) / count
touched = -1
distance = float("inf")
for index in range(count + 1):
price = lower + index * step
if price < float(candle.LowPrice) or price > float(candle.HighPrice):
continue
candidate_distance = abs(price - float(candle.ClosePrice))
if candidate_distance < distance:
touched = index
distance = candidate_distance
return touched
def CreateClone(self):
return grid_bot_strategy()