RangeEA Weekly Grid Strategy is a limit-order grid system converted from the original MetaTrader expert advisor. The algorithm identifies the current
weekly trading range and populates it with a configurable number of pending limit orders. Each order uses dynamic stop-loss and
take-profit offsets scaled relative to the distance between the limit price and the current market price while also respecting
minimal distances expressed in points. Profits can be locked by closing the entire book once the portfolio equity grows by a
predefined percentage.
The implementation leverages StockSharp's high-level API: candles drive the decision logic, pending orders are managed with the
strategy helper methods, and risk controls are exposed as optimization-ready parameters.
Trading Logic
Subscribe to two candle streams:
A user-defined timeframe (1-hour by default) that drives grid maintenance.
Weekly candles that are used to estimate the current trading range.
For every finished weekly candle, update the highest high and lowest low across the last two weeks. Their difference becomes
the active trading range.
On each finished trading candle:
Respect the configured trading window (StartTradeHour to EndTradeHour).
Optionally reset the grid at the beginning of each trading day.
If no pending limit orders exist, distribute new orders evenly between the range low and the range high.
After two orders have already been executed, replace the second-last fill with a new order at the same price when the grid
shrinks to NumberOfOrders - 2 items.
Continuously monitor the account equity and liquidate everything when the configured profit percentage is reached.
When the trading window closes and CloseAllAtEndTrade is enabled, cancel every pending order and exit existing positions.
Parameters
Name
Description
Default
CandleType
Trading timeframe used to trigger grid maintenance.
1 hour candles
WeeklyCandleType
Timeframe used to derive the range boundaries.
1 week candles
StartTradeHour
Hour of day when new orders may be placed.
0
EndTradeHour
Hour of day when trading stops.
24
CloseAllAtEndTrade
Close all orders and positions outside of the trading window.
true
MaxOpenOrders
Maximum number of simultaneous orders and positions.
5
NumberOfOrders
Number of limit orders in the grid.
10
OrderVolume
Volume used for each order.
0.01
ResetOrdersDaily
Rebuild the grid at the start of each trading day.
true
StopLossPoints
Minimum stop-loss distance in points.
60
TakeProfitPoints
Minimum take-profit distance in points.
60
StopLossMultiplier
Multiplier applied to the dynamic stop-loss distance.
3
TakeProfitMultiplier
Multiplier applied to the dynamic take-profit distance.
1
TargetPercentage
Equity gain percentage that triggers liquidation.
8
Risk Management
The strategy honours the MaxOpenOrders limit to keep the number of active orders and positions under control.
Stop-loss and take-profit levels are always at least the configured number of points away from the entry and can optionally be
extended by the multiplier parameters.
The daily reset option prevents stale orders from being carried into a new session.
A portfolio-level equity target allows the strategy to lock in profits by flattening the book.
Notes
Ensure the selected security provides weekly candles; otherwise the strategy cannot compute the range.
When using instruments with non-standard price steps, adjust the point-based settings to match the underlying tick size.
Optimizing NumberOfOrders, OrderVolume, and the stop/take multipliers helps adapt the grid to different levels of
volatility.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Range based grid strategy that detects the trading range from recent price action
/// and places buy/sell limit orders at grid levels within the range.
/// Buys at lower grid levels, sells at upper grid levels.
/// </summary>
public class RangeWeeklyGridStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rangePeriod;
private readonly StrategyParam<int> _gridLevels;
private decimal _rangeHigh;
private decimal _rangeLow;
private bool _rangeSet;
private decimal _entryPrice;
private DateTimeOffset _lastTradeTime;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RangePeriod
{
get => _rangePeriod.Value;
set => _rangePeriod.Value = value;
}
public int GridLevels
{
get => _gridLevels.Value;
set => _gridLevels.Value = value;
}
public RangeWeeklyGridStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary candle type", "General");
_rangePeriod = Param(nameof(RangePeriod), 100)
.SetDisplay("Range Period", "Number of candles to determine range", "Logic");
_gridLevels = Param(nameof(GridLevels), 5)
.SetDisplay("Grid Levels", "Number of grid levels within the range", "Logic");
}
protected override void OnReseted()
{
base.OnReseted();
_rangeHigh = 0;
_rangeLow = 0;
_rangeSet = false;
_entryPrice = 0;
_lastTradeTime = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var highest = new Highest { Length = RangePeriod };
var lowest = new Lowest { Length = RangePeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue)
{
if (candle.State != CandleStates.Finished)
return;
if (highestValue <= 0 || lowestValue <= 0 || highestValue <= lowestValue)
return;
_rangeHigh = highestValue;
_rangeLow = lowestValue;
_rangeSet = true;
if (!_rangeSet)
return;
var range = _rangeHigh - _rangeLow;
if (range <= 0)
return;
// Cooldown: at least 1 day between trades
if (_lastTradeTime != default && candle.CloseTime - _lastTradeTime < TimeSpan.FromDays(1))
return;
var gridStep = range / (GridLevels + 1);
var close = candle.ClosePrice;
var mid = (_rangeHigh + _rangeLow) / 2;
// Buy when price is in lower portion of range
if (close <= _rangeLow + gridStep && Position <= 0)
{
BuyMarket();
_entryPrice = close;
_lastTradeTime = candle.CloseTime;
}
// Sell when price is in upper portion of range
else if (close >= _rangeHigh - gridStep && Position >= 0)
{
SellMarket();
_entryPrice = close;
_lastTradeTime = candle.CloseTime;
}
// Take profit at mid-range
else if (Position > 0 && close >= mid)
{
SellMarket();
_lastTradeTime = candle.CloseTime;
}
else if (Position < 0 && close <= mid)
{
BuyMarket();
_lastTradeTime = candle.CloseTime;
}
}
}
import clr
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class range_weekly_grid_strategy(Strategy):
def __init__(self):
super(range_weekly_grid_strategy, self).__init__()
self._range_period = self.Param("RangePeriod", 100).SetDisplay("Range Period", "Candles to determine range", "Logic")
self._grid_levels = self.Param("GridLevels", 5).SetDisplay("Grid Levels", "Grid levels within range", "Logic")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Primary candle type", "General")
@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(range_weekly_grid_strategy, self).OnStarted2(time)
highest = Highest()
highest.Length = self._range_period.Value
lowest = Lowest()
lowest.Length = self._range_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(highest, lowest, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle, highest_val, lowest_val):
if candle.State != CandleStates.Finished:
return
if highest_val <= 0 or lowest_val <= 0 or highest_val <= lowest_val:
return
rng = highest_val - lowest_val
if rng <= 0:
return
grid_step = rng / (self._grid_levels.Value + 1)
close = candle.ClosePrice
mid = (highest_val + lowest_val) / 2.0
if close <= lowest_val + grid_step and self.Position <= 0:
self.BuyMarket()
elif close >= highest_val - grid_step and self.Position >= 0:
self.SellMarket()
elif self.Position > 0 and close >= mid:
self.SellMarket()
elif self.Position < 0 and close <= mid:
self.BuyMarket()
def CreateClone(self):
return range_weekly_grid_strategy()