在 GitHub 上查看
RangeEA 周期网格策略
概览
RangeEA 周期网格策略是一套从 MetaTrader 专家顾问移植的限价网格系统。它会计算最近两周的最高价和最低价,从而获得
当前的周区间,并在区间内部按均匀间距布置预设数量的限价挂单。每张挂单的止损和止盈距离都会根据
挂单价格与当前价格之间的差距进行动态缩放,同时确保不少于指定的最小点数。当账户权益达到设定的
收益百分比时,策略会立即平仓并撤销所有挂单,以锁定利润。
策略通过 StockSharp 的高级 API 实现:使用蜡烛图驱动逻辑、借助内置方法管理挂单,并将所有重要参数暴露
出来以便优化。
交易流程
- 订阅两组蜡烛数据:
- 可配置的交易周期(默认 1 小时),用于维护网格。
- 周期为 1 周的蜡烛图,用于估算交易区间。
- 每当新的周蜡烛收盘时,更新最近两周的最高价与最低价,二者的差值即为当前网格范围。
- 在每根交易蜡烛收盘时执行以下步骤:
- 检查是否处于允许交易的时间窗口(
StartTradeHour 至 EndTradeHour)。
- 如果启用了每日重置,则在新交易日开始时清除旧的挂单。
- 当没有任何限价挂单时,在区间内重新分布
NumberOfOrders 张挂单。
- 如果已有至少两张挂单成交,当网格规模缩小到
NumberOfOrders - 2 张时,会在倒数第二个成交价位重新放置
一张挂单。
- 持续监控账户权益,一旦达到
TargetPercentage 指定的收益率,就全部平仓并撤单。
- 当交易时间结束且
CloseAllAtEndTrade 为真时,策略会取消所有挂单并平掉持仓。
参数说明
| 名称 |
说明 |
默认值 |
CandleType |
触发策略逻辑的交易蜡烛周期。 |
1 小时蜡烛 |
WeeklyCandleType |
计算区间所使用的蜡烛周期。 |
1 周蜡烛 |
StartTradeHour |
可以开始下单的小时数。 |
0 |
EndTradeHour |
停止交易的小时数。 |
24 |
CloseAllAtEndTrade |
交易时间之外是否平仓并撤单。 |
true |
MaxOpenOrders |
同时允许存在的订单和持仓数量上限。 |
5 |
NumberOfOrders |
网格中的限价挂单数量。 |
10 |
OrderVolume |
每张挂单的成交量。 |
0.01 |
ResetOrdersDaily |
是否在每天开始时重建网格。 |
true |
StopLossPoints |
止损距离的最小点数。 |
60 |
TakeProfitPoints |
止盈距离的最小点数。 |
60 |
StopLossMultiplier |
动态止损距离的乘数。 |
3 |
TakeProfitMultiplier |
动态止盈距离的乘数。 |
1 |
TargetPercentage |
触发全部平仓的收益百分比。 |
8 |
风险控制
MaxOpenOrders 保证挂单和持仓数量不会无限增加。
- 止损与止盈距离至少为设定的最小点数,并可通过乘数参数进一步放大。
- 每日重置可以防止过期的挂单延续到新交易日。
- 权益目标机制能够在达到收益目标后及时锁定利润。
备注
- 需要确保标的提供周蜡烛数据,否则无法计算交易区间。
- 如果标的的最小价格跳动与默认点值不同,应相应调整点数相关参数。
- 可以通过优化
NumberOfOrders、OrderVolume 以及止损/止盈乘数来适配不同波动水平的市场。
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()