Ver no GitHub

RangeEA Weekly Grid Strategy

Overview

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

  1. 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.
  2. For every finished weekly candle, update the highest high and lowest low across the last two weeks. Their difference becomes the active trading range.
  3. 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.
  4. 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;
		}
	}
}