在 GitHub 上查看
区间跟随策略
概览
区间跟随策略在 StockSharp 高级 API 上复现了 MetaTrader 5 专家顾问 “Range Follower”。策略利用日线的平均真实波幅 (ATR) 作为波动性基准,只要价格在当日内远离日内高点或低点达到指定阈值,便开立一次突破交易。ATR 被划分为触发段与剩余段,触发段用于判断是否入场,剩余段用于设置止盈距离,从而完整保留了原始 EA 的设计。
交易逻辑
- 日内波动基准
- 在日线数据上计算 20 周期 ATR,得到当前交易日的参考区间。
TriggerPercent 参数把 ATR 拆分为两部分:触发距离(入场阈值)以及剩余距离(止盈目标)。
- 区间跟踪
- 通过日线订阅实时更新当前交易日的最高价与最低价。
- Level1 行情提供最新买一与卖一,用于计算报价相对于日内极值的距离。
- 每日仅一次入场
- 若买一价高于当日最低价的距离超过触发距离,且当天尚未建仓,则以市价买入。
- 若卖一价低于当日最高价的距离超过触发距离,且当天尚未建仓,则以市价卖出。
- 每个交易日只允许一次交易,日切换时会重置该标志。
- 止损与止盈
- 多头仓位的止损设置在入场价下方一个触发距离,止盈设置在入场价上方一个剩余距离。
- 空头仓位的止损设置在入场价上方一个触发距离,止盈设置在入场价下方一个剩余距离。
- 通过 Level1 价格与蜡烛收盘共同监控,一旦触及任一价格立即平仓。
- 日度重置
- 当出现新交易日的第一根蜡烛时,策略会平掉所有持仓,清空内部状态并重新加载 ATR。
- 如果在当日初始化时发现实际区间已经超过触发距离,则当天暂停交易,以复制原 EA 的安全检查。
参数
| 名称 |
默认值 |
说明 |
CandleType |
15 分钟蜡烛 |
用于识别交易日边界的工作级别。 |
TriggerPercent |
60 |
ATR 中用于触发的百分比,必须处于 10 到 90 之间。 |
Volume |
0.1 |
多空方向的市价下单数量。 |
风险管理
- 止损与止盈均来源于同一 ATR 基准,回报风险比始终为
(100 - TriggerPercent) : TriggerPercent。
- 策略最多仅持有一个仓位,止损或止盈被触发后立即平仓,避免叠加敞口。
- 调用
StartProtection() 以启用 StockSharp 的保护机制,便于额外挂载移动止损或组合风控模块。
实现细节
- 通过单独的日线订阅与
AverageTrueRange 指标获取 ATR 数值,全部使用高阶 Bind 接口完成。
- Level1 数据用于还原 EA 以 tick 为驱动的入场与离场判断,买一和卖一价格直接驱动交易决策。
- 交易日边界来自工作级别蜡烛,确保不同交易日历下都能一致地重置策略状态。
- 转换过程中未使用自定义指标缓存或循环历史数据,而是通过字段保存状态以符合项目规范。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Range Follower strategy: ATR-based range breakout.
/// Tracks high/low range and enters when price breaks out by ATR threshold.
/// </summary>
public class RangeFollowerStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _rangePeriod;
private decimal _rangeHigh;
private decimal _rangeLow;
private int _barCount;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public int RangePeriod { get => _rangePeriod.Value; set => _rangePeriod.Value = value; }
public RangeFollowerStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Indicators");
_rangePeriod = Param(nameof(RangePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Range Period", "Bars for range calculation", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rangeHigh = 0m;
_rangeLow = decimal.MaxValue;
_barCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rangeHigh = 0;
_rangeLow = decimal.MaxValue;
_barCount = 0;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(atr, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
_barCount++;
if (candle.HighPrice > _rangeHigh) _rangeHigh = candle.HighPrice;
if (candle.LowPrice < _rangeLow) _rangeLow = candle.LowPrice;
if (_barCount < RangePeriod) return;
var threshold = atrValue * 0.5m;
if (close > _rangeHigh - threshold && Position <= 0)
{
BuyMarket();
ResetRange();
}
else if (close < _rangeLow + threshold && Position >= 0)
{
SellMarket();
ResetRange();
}
// Slide the range window
if (_barCount > RangePeriod * 2)
ResetRange();
}
private void ResetRange()
{
_rangeHigh = 0;
_rangeLow = decimal.MaxValue;
_barCount = 0;
}
}
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 AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class range_follower_strategy(Strategy):
def __init__(self):
super(range_follower_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._atr_period = self.Param("AtrPeriod", 14)
self._range_period = self.Param("RangePeriod", 20)
self._range_high = 0.0
self._range_low = float('inf')
self._bar_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def RangePeriod(self):
return self._range_period.Value
@RangePeriod.setter
def RangePeriod(self, value):
self._range_period.Value = value
def OnReseted(self):
super(range_follower_strategy, self).OnReseted()
self._range_high = 0.0
self._range_low = float('inf')
self._bar_count = 0
def OnStarted2(self, time):
super(range_follower_strategy, self).OnStarted2(time)
self._range_high = 0.0
self._range_low = float('inf')
self._bar_count = 0
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self._process_candle).Start()
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
self._bar_count += 1
if float(candle.HighPrice) > self._range_high:
self._range_high = float(candle.HighPrice)
if float(candle.LowPrice) < self._range_low:
self._range_low = float(candle.LowPrice)
if self._bar_count < self.RangePeriod:
return
threshold = float(atr_value) * 0.5
if close > self._range_high - threshold and self.Position <= 0:
self.BuyMarket()
self._reset_range()
elif close < self._range_low + threshold and self.Position >= 0:
self.SellMarket()
self._reset_range()
if self._bar_count > self.RangePeriod * 2:
self._reset_range()
def _reset_range(self):
self._range_high = 0.0
self._range_low = float('inf')
self._bar_count = 0
def CreateClone(self):
return range_follower_strategy()