在 GitHub 上查看

CoensioTrader1 V06 策略

概述

CoensioTrader1 V06 最初是一款 MetaTrader 专家顾问(EA),通过识别布林带假突破并结合趋势过滤进行交易。本次 StockSharp 迁移保留了核心形态逻辑,删除了与经纪商、DLL 授权及远程服务器通信相关的全部代码。策略仅针对单一品种与时间框架运行,依靠布林带与双指数移动平均线(DEMA)来捕捉趋势恢复。

原始 EA 支持最多六个货币对的独立参数配置,并会将优化结果上传至作者服务器。迁移版本聚焦于进出场流程,忽略所有外部依赖,以方便回测与进一步定制。

策略逻辑

  1. 数据订阅:订阅指定的 K 线类型(默认 1 小时),并绑定布林带与 DEMA 指标。
  2. 布林带反弹判定:所有条件基于最近一根完整收盘 K 线。
    • 多头条件
      • K 线开盘价位于前一根布林带下轨之下,但收盘价重新站上该下轨(假突破)。
      • 当前 K 线的最低价高于前一根 K 线的最低价,同时前一根 K 线的最低价低于再前一根 K 线(双底结构)。
      • DEMA 三个连续值呈上升状态(当前值 > 前一个值 > 再前一个值)。
    • 空头条件
      • K 线开盘价位于前一根布林带上轨之上,但收盘价跌回上轨之下(假突破)。
      • 当前 K 线的最高价低于前一根 K 线的最高价,同时前一根 K 线的最高价高于再前一根 K 线(双顶结构)。
      • DEMA 三个连续值呈下降状态。
  3. 下单执行:在条件成立的下一根 K 线开盘时立即发送市价单。若启用 CloseOnSignal,则在反向信号出现时会先平掉原有仓位。
  4. 风控设置:通过 StartProtection 提供可选的止损与止盈距离,使用绝对价格差。原版 EA 的逐步跟踪止损功能未移植。

参数说明

参数 作用 默认值
BollingerPeriod 布林带周期长度。 30
BollingerDeviation 布林带标准差倍数。 1.5
DemaPeriod DEMA 周期,用于趋势判定。 20
StopLossDistance 传递给 StartProtection 的绝对止损距离,0 表示关闭。 0 (绝对值)
TakeProfitDistance 传递给 StartProtection 的绝对止盈距离,0 表示关闭。 0 (绝对值)
CloseOnSignal 是否在出现反向信号时先行平仓。 false
CandleType 使用的 K 线类型/周期。 1 小时

使用提示

  • StockSharp 版本仅交易主符号,如需多品种交易,请分别启动多个策略实例并设置不同参数。
  • MQL 中的仓位 sizing(RiskMaxLotSizeLotBalanceDivider)未移植,请在策略属性中设置 Volume 或结合自定义风控模块。
  • 迁移中移除了 DLL 授权检查、HTTP 通讯和图形注释。需要可视化时,可使用 StockSharp 的绘图助手。
  • 止损与止盈参数为绝对价格差,请结合标的品种的点值或最小变动单位进行换算。
  • 原有的拖尾止损步进机制未实现,如需动态保护可叠加额外的风险管理逻辑。
  • 代码内注释全部使用英文,方便与仓库中其他策略保持一致;中文、英文、俄文说明分列于 README。

与原始 MQL 版本的差异

  • 多品种管理:改为单品种,实现更清晰的状态管理与测试流程。
  • 网络与授权:全部删除,无外部 HTTP 请求或 DLL 调用。
  • 下单手数:依赖 StockSharp 默认的 Volume 处理方式。
  • 图形绘制:未复刻 MetaTrader 中的箭头、文字标签与配色方案。
  • 拖尾止损:未移植,仅提供初始保护单设置。

本文档力求详尽,使读者在不参考原始 MQL 代码的情况下即可理解并扩展该策略。

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the CoensioTrader1 V06 MQL strategy.
/// The strategy buys after a lower Bollinger Band rejection paired with a higher low pattern and bullish DEMA trend.
/// It sells after an upper Bollinger Band rejection with a lower high structure and bearish DEMA trend.
/// </summary>
public class CoensioTrader1V06Strategy : Strategy
{
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerDeviation;
	private readonly StrategyParam<int> _demaPeriod;
	private readonly StrategyParam<Unit> _stopLossDistance;
	private readonly StrategyParam<Unit> _takeProfitDistance;
	private readonly StrategyParam<bool> _closeOnSignal;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevOpen;
	private decimal? _prevHigh;
	private decimal? _prevLow;
	private decimal? _prevClose;

	private decimal? _prev2High;
	private decimal? _prev2Low;
	private decimal? _prev3High;
	private decimal? _prev3Low;

	private decimal? _prevUpperBand;
	private decimal? _prevLowerBand;

	private decimal? _prevDema;
	private decimal? _prev2Dema;

	/// <summary>
	/// Initializes a new instance of the <see cref="CoensioTrader1V06Strategy"/> class.
	/// </summary>
	public CoensioTrader1V06Strategy()
	{
		_bollingerPeriod = Param(nameof(BollingerPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Period", "Length of Bollinger Bands", "Indicators")
			;

		_bollingerDeviation = Param(nameof(BollingerDeviation), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Deviation", "Standard deviation multiplier", "Indicators")
			;

		_demaPeriod = Param(nameof(DemaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("DEMA Period", "Length of double exponential moving average", "Indicators")
			;

		_stopLossDistance = Param(nameof(StopLossDistance), new Unit(0m, UnitTypes.Absolute))
			.SetDisplay("Stop Loss", "Absolute stop loss offset from entry", "Risk");

		_takeProfitDistance = Param(nameof(TakeProfitDistance), new Unit(0m, UnitTypes.Absolute))
			.SetDisplay("Take Profit", "Absolute take profit offset from entry", "Risk");

		_closeOnSignal = Param(nameof(CloseOnSignal), false)
			.SetDisplay("Close On Opposite Signal", "Close current trades when opposite setup appears", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for signal calculations", "General");

		Volume = 0.01m;
	}

	/// <summary>
	/// Bollinger Bands period.
	/// </summary>
	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	/// <summary>
	/// Bollinger Bands standard deviation multiplier.
	/// </summary>
	public decimal BollingerDeviation
	{
		get => _bollingerDeviation.Value;
		set => _bollingerDeviation.Value = value;
	}

	/// <summary>
	/// Double exponential moving average period.
	/// </summary>
	public int DemaPeriod
	{
		get => _demaPeriod.Value;
		set => _demaPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance from entry price.
	/// </summary>
	public Unit StopLossDistance
	{
		get => _stopLossDistance.Value;
		set => _stopLossDistance.Value = value;
	}

	/// <summary>
	/// Take-profit distance from entry price.
	/// </summary>
	public Unit TakeProfitDistance
	{
		get => _takeProfitDistance.Value;
		set => _takeProfitDistance.Value = value;
	}

	/// <summary>
	/// Close current position when an opposite signal appears.
	/// </summary>
	public bool CloseOnSignal
	{
		get => _closeOnSignal.Value;
		set => _closeOnSignal.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevOpen = null;
		_prevHigh = null;
		_prevLow = null;
		_prevClose = null;

		_prev2High = null;
		_prev2Low = null;
		_prev3High = null;
		_prev3Low = null;

		_prevUpperBand = null;
		_prevLowerBand = null;

		_prevDema = null;
		_prev2Dema = null;
	}

	/// <inheritdoc />
	private BollingerBands _bollinger;
	private DoubleExponentialMovingAverage _dema;

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_bollinger = new BollingerBands
		{
			Length = BollingerPeriod,
			Width = BollingerDeviation
		};

		_dema = new DoubleExponentialMovingAverage
		{
			Length = DemaPeriod
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var bollingerResult = _bollinger.Process(candle);
		var demaResult = _dema.Process(candle);

		if (bollingerResult.IsEmpty || demaResult.IsEmpty || !_bollinger.IsFormed || !_dema.IsFormed)
		{
			_prevOpen = candle.OpenPrice;
			_prevClose = candle.ClosePrice;
			_prevLow = candle.LowPrice;
			_prevHigh = candle.HighPrice;
			return;
		}

		var bollingerValue = (BollingerBandsValue)bollingerResult;
		var upper = bollingerValue.UpBand ?? 0m;
		var lower = bollingerValue.LowBand ?? 0m;
		var demaValue = demaResult.GetValue<decimal>();

		if (_prevOpen.HasValue && _prevClose.HasValue && _prevLow.HasValue && _prevHigh.HasValue &&
			_prevLowerBand.HasValue && _prevUpperBand.HasValue && _prevDema.HasValue)
		{
			// Long setup: lower band rejection with rising DEMA.
			var crossedLowerBand = _prevLow.Value <= _prevLowerBand.Value && _prevClose.Value > _prevLowerBand.Value;
			var bullishTrend = demaValue > _prevDema.Value;

			if (crossedLowerBand && bullishTrend)
			{
				if (CloseOnSignal && Position < 0)
					{
					if (Position > 0) SellMarket(Position);
					else if (Position < 0) BuyMarket(-Position);
				}

				if (Position <= 0)
					BuyMarket();
			}

			// Short setup: upper band rejection with falling DEMA.
			var crossedUpperBand = _prevHigh.Value >= _prevUpperBand.Value && _prevClose.Value < _prevUpperBand.Value;
			var bearishTrend = demaValue < _prevDema.Value;

			if (crossedUpperBand && bearishTrend)
			{
				if (CloseOnSignal && Position > 0)
					{
					if (Position > 0) SellMarket(Position);
					else if (Position < 0) BuyMarket(-Position);
				}

				if (Position >= 0)
					SellMarket();
			}
		}

		_prev3Low = _prev2Low;
		_prev3High = _prev2High;
		_prev2Low = _prevLow;
		_prev2High = _prevHigh;

		_prevLowerBand = lower;
		_prevUpperBand = upper;

		_prev2Dema = _prevDema;
		_prevDema = demaValue;

		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
		_prevLow = candle.LowPrice;
		_prevHigh = candle.HighPrice;
	}
}