在 GitHub 上查看
DeMarker 增量仓位策略
概述
本策略是 MetaTrader 专家顾问 “DeMarker gaining position volume” 的 StockSharp 版本。策略利用 DeMarker 振荡指标识别超买与超卖极值,在行情保持极端状态时逐步增加头寸。实现基于已完成的 K 线运行,每根 K 线只响应一次信号。
C# 版本保留了原始脚本的核心交易思路,并结合了 StockSharp 的高级 API。通过参数可以控制下单量、加仓方式以及是否反向交易,使策略能够适配不同市场与周期。
参数
- DeMarker Period —— DeMarker 指标的计算周期。
- Upper Level —— 触发做空仓位的阈值(默认
0.7)。
- Lower Level —— 触发做多仓位的阈值(默认
0.3)。
- Trade Volume —— 每次信号下达的市价单数量。
- Only One Position —— 启用后,在开新仓前会先平掉当前持仓,确保不会同时持有多头和空头。
- Reverse Signals —— 互换买卖条件,可将策略调整为顺势或逆势版本。
- Candle Type —— 指标与信号计算所使用的 K 线周期。
交易逻辑
- 根据选定的周期订阅 K 线,并将数据绑定到 DeMarker 指标。
- 当最新完成的 K 线收盘时,读取当前 DeMarker 数值并与设定阈值比较。
- 正常模式下:
- DeMarker 低于下阈值时尝试建立或加仓多头。
- DeMarker 高于上阈值时尝试建立或加仓空头。
- 开启反向模式后,阈值含义互换(极低值触发做空,极高值触发做多)。
- 策略会记录最近一笔成交所属 K 线,避免同一根 K 线重复入场。
仓位管理
- 在反向开仓前,会根据当前 K 线价格计算既有头寸的浮动盈亏。只有当离场能够获得正收益时才会平掉旧仓,以模拟原始 EA 的保护机制。
- 策略内部跟踪多头与空头的平均成本价。追加仓位时会重新计算平均价格,以便准确判断盈利情况。
- 可选的 Only One Position 参数适用于净头寸模式,确保每次入场前都回到空仓状态。
- 启动时调用
StartProtection(),在策略被停止但仍有持仓时提供紧急平仓能力。
注意事项
- 本移植版本完全基于 StockSharp 高级 API,不使用自定义数据集合,也不会直接轮询指标值。
- MetaTrader 版本中的资金管理(固定保证金、按百分比风控等)被简化为恒定的
Trade Volume 参数。若需要动态风控,请在策略外部处理。
- 市价单默认按 K 线收盘价模拟成交,实际运行时应根据经纪商执行质量和滑点情况进行验证与调整。
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Uses the DeMarker oscillator to accumulate positions when extreme levels are reached.
/// </summary>
public class DeMarkerGainingPositionVolumeStrategy : Strategy
{
private readonly StrategyParam<int> _deMarkerPeriod;
private readonly StrategyParam<decimal> _upperLevel;
private readonly StrategyParam<decimal> _lowerLevel;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private decimal? _prevOscillator;
/// <summary>
/// Number of candles used by the DeMarker indicator.
/// </summary>
public int DeMarkerPeriod
{
get => _deMarkerPeriod.Value;
set => _deMarkerPeriod.Value = value;
}
/// <summary>
/// DeMarker level that triggers short entries.
/// </summary>
public decimal UpperLevel
{
get => _upperLevel.Value;
set => _upperLevel.Value = value;
}
/// <summary>
/// DeMarker level that triggers long entries.
/// </summary>
public decimal LowerLevel
{
get => _lowerLevel.Value;
set => _lowerLevel.Value = value;
}
/// <summary>
/// Candle type that defines the timeframe for the oscillator.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public DeMarkerGainingPositionVolumeStrategy()
{
_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 14)
.SetDisplay("DeMarker Period", "Number of bars used by the oscillator.", "Indicator");
_upperLevel = Param(nameof(UpperLevel), 0.7m)
.SetDisplay("Upper Level", "Threshold that prepares short exposure.", "Indicator");
_lowerLevel = Param(nameof(LowerLevel), 0.3m)
.SetDisplay("Lower Level", "Threshold that prepares long exposure.", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for DeMarker calculations.", "Data");
}
/// <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;
}
var oscillatorValue = rsiValue / 100m;
if (_prevOscillator is null)
{
_prevOscillator = oscillatorValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Oscillator crosses below the lower level => oversold => buy
if (_prevOscillator > LowerLevel && oscillatorValue <= LowerLevel)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
// Oscillator crosses above the upper level => overbought => sell
else if (_prevOscillator < UpperLevel && oscillatorValue >= UpperLevel)
{
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_gaining_position_volume_strategy(Strategy):
def __init__(self):
super(de_marker_gaining_position_volume_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._demarker_period = self.Param("DeMarkerPeriod", 14)
self._upper_level = self.Param("UpperLevel", 0.7)
self._lower_level = self.Param("LowerLevel", 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 UpperLevel(self):
return self._upper_level.Value
@UpperLevel.setter
def UpperLevel(self, value):
self._upper_level.Value = value
@property
def LowerLevel(self):
return self._lower_level.Value
@LowerLevel.setter
def LowerLevel(self, value):
self._lower_level.Value = value
def OnReseted(self):
super(de_marker_gaining_position_volume_strategy, self).OnReseted()
self._prev_osc = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(de_marker_gaining_position_volume_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.UpperLevel)
lower = float(self.LowerLevel)
if self._has_prev:
# Cross below lower level => oversold => buy
if self._prev_osc > lower and osc_val <= lower and self.Position <= 0:
self.BuyMarket()
# Cross above upper level => overbought => 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_gaining_position_volume_strategy()