在 GitHub 上查看
DeMarker Pending 2 策略
概述
该策略使用 StockSharp 高级 API 复刻 MetaTrader 专家顾问“DeMarker Pending 2”的核心思路。系统在设定的时间框架上计算 DeMarker 振荡指标,并在指标穿越自定义阈值时准备挂单买入或卖出。下单方式可选择止损单或限价单,并在当前市价基础上加入额外偏移。策略还提供交易时段过滤、价差限制以及距离控制,用于管理入场条件。
交易逻辑
- 订阅所需的K线序列,并以给定周期计算 DeMarker 指标。
- 当上一笔指标值高于下轨且当前值跌破下轨时,排队准备多头挂单;当上一笔指标值低于上轨且当前值突破上轨时,排队准备空头挂单。每根K线只处理一个信号。
- 挂单按照“偏移点数”转换成价格,支持止损单或限价单。若启用替换选项,则在发送新挂单前取消旧单。策略限制“持仓 + 挂单”的总数量,并检查新的挂单价与当前持仓均价之间的最小距离。
- 多空头寸可启用止损、止盈和跟踪止损。防护价格以点数计算,并在每根收盘K线上检测。当利润达到激活条件并再获得额外的跟踪步长后,跟踪止损会向有利方向移动。
- 如果当前最优买卖价价差超过阈值,则跳过下单。可选的交易时段过滤器会在非交易时间禁止新挂单。
参数
| 名称 |
说明 |
| Working Candles |
用于生成信号和检查风控的时间框架。 |
| Order Volume |
挂单的默认下单量。 |
| Stop Loss (pts) |
止损距离,单位为价格点。 |
| Take Profit (pts) |
止盈距离,单位为价格点。 |
| Trailing Activate (pts) |
启动跟踪止损所需的盈利点数。 |
| Trailing Stop (pts) |
跟踪止损与当前价格之间的距离。 |
| Trailing Step (pts) |
每次移动跟踪止损所需的额外盈利。 |
| Trail On Close |
若启用,仅在K线收盘后更新跟踪止损。 |
| Max Positions |
持仓与挂单的最大合计数量,0 表示不限制。 |
| Min Distance (pts) |
新挂单价格与当前持仓均价之间的最小距离。 |
| Use Stop Orders |
为 true 时使用止损挂单,否则使用限价挂单。 |
| Single Pending |
只允许存在一张活动挂单。 |
| Replace Pendings |
发送新挂单前先取消旧挂单。 |
| Pending Offset (pts) |
相对于当前市价的挂单偏移点数。 |
| Max Spread (pts) |
允许的最大买卖价差,超出则放弃下单。 |
| Use Session Filter |
是否启用交易时段过滤。 |
| Start Hour/Minute, End Hour/Minute |
启用交易窗口后使用的起止时间。 |
| DeMarker Period |
DeMarker 指标的平均周期。 |
| Upper Level |
触发做空信号的阈值。 |
| Lower Level |
触发做多信号的阈值。 |
说明
- 原始 EA 中的挂单到期时间与风险资金管理未被移植,此处使用固定下单量。
- 止损与止盈在K线收盘时通过最高价/最低价判定,可能与 MetaTrader 中的盘中执行存在差异。
- 跟踪止损仅在K线收盘时计算,未实现逐笔行情的实时移动。
- 挂单依赖行情源提供的最优买卖价,请确保 Level1 行情订阅可用。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Pending order strategy driven by the DeMarker oscillator.
/// Simplified from "DeMarker Pending 2" to use market orders on threshold crossovers.
/// </summary>
public class DeMarkerPending2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _demarkerPeriod;
private readonly StrategyParam<decimal> _demarkerUpperLevel;
private readonly StrategyParam<decimal> _demarkerLowerLevel;
private RelativeStrengthIndex _rsi;
private decimal? _prevOscillator;
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// DeMarker oscillator period.
/// </summary>
public int DemarkerPeriod
{
get => _demarkerPeriod.Value;
set => _demarkerPeriod.Value = value;
}
/// <summary>
/// Upper DeMarker threshold for sell signal.
/// </summary>
public decimal DemarkerUpperLevel
{
get => _demarkerUpperLevel.Value;
set => _demarkerUpperLevel.Value = value;
}
/// <summary>
/// Lower DeMarker threshold for buy signal.
/// </summary>
public decimal DemarkerLowerLevel
{
get => _demarkerLowerLevel.Value;
set => _demarkerLowerLevel.Value = value;
}
public DeMarkerPending2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for calculations", "General");
_demarkerPeriod = Param(nameof(DemarkerPeriod), 14)
.SetDisplay("DeMarker Period", "DeMarker oscillator period", "Indicator")
.SetGreaterThanZero();
_demarkerUpperLevel = Param(nameof(DemarkerUpperLevel), 0.7m)
.SetDisplay("Upper Level", "Overbought threshold", "Indicator");
_demarkerLowerLevel = Param(nameof(DemarkerLowerLevel), 0.3m)
.SetDisplay("Lower Level", "Oversold threshold", "Indicator");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevOscillator = null;
_rsi = new RelativeStrengthIndex { Length = DemarkerPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed)
{
_prevOscillator = rsiValue / 100m;
return;
}
if (_prevOscillator is not decimal prev)
{
_prevOscillator = rsiValue / 100m;
return;
}
var oscillatorValue = rsiValue / 100m;
var volume = Volume;
if (volume <= 0)
volume = 1;
// Cross above lower level from below => buy
if (prev < DemarkerLowerLevel && oscillatorValue >= DemarkerLowerLevel)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
// Cross below upper level from above => sell
else if (prev > DemarkerUpperLevel && oscillatorValue <= DemarkerUpperLevel)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevOscillator = oscillatorValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_rsi = null;
_prevOscillator = null;
base.OnReseted();
}
}
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 RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class de_marker_pending2_strategy(Strategy):
def __init__(self):
super(de_marker_pending2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._demarker_period = self.Param("DemarkerPeriod", 14)
self._demarker_upper_level = self.Param("DemarkerUpperLevel", 0.7)
self._demarker_lower_level = self.Param("DemarkerLowerLevel", 0.3)
self._prev_osc = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def DemarkerPeriod(self):
return self._demarker_period.Value
@DemarkerPeriod.setter
def DemarkerPeriod(self, value):
self._demarker_period.Value = value
@property
def DemarkerUpperLevel(self):
return self._demarker_upper_level.Value
@DemarkerUpperLevel.setter
def DemarkerUpperLevel(self, value):
self._demarker_upper_level.Value = value
@property
def DemarkerLowerLevel(self):
return self._demarker_lower_level.Value
@DemarkerLowerLevel.setter
def DemarkerLowerLevel(self, value):
self._demarker_lower_level.Value = value
def OnReseted(self):
super(de_marker_pending2_strategy, self).OnReseted()
self._prev_osc = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(de_marker_pending2_strategy, self).OnStarted2(time)
self._prev_osc = 0.0
self._has_prev = False
rsi = RelativeStrengthIndex()
rsi.Length = self.DemarkerPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self._process_candle).Start()
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
osc_val = float(rsi_value) / 100.0
upper = float(self.DemarkerUpperLevel)
lower = float(self.DemarkerLowerLevel)
if self._has_prev:
# Cross above lower from below => buy
if self._prev_osc < lower and osc_val >= lower and self.Position <= 0:
self.BuyMarket()
# Cross below upper from above => sell
elif self._prev_osc > upper and osc_val <= upper and self.Position >= 0:
self.SellMarket()
self._prev_osc = osc_val
self._has_prev = True
def CreateClone(self):
return de_marker_pending2_strategy()