CoensioTrader1 V06 策略
概述
CoensioTrader1 V06 最初是一款 MetaTrader 专家顾问(EA),通过识别布林带假突破并结合趋势过滤进行交易。本次 StockSharp 迁移保留了核心形态逻辑,删除了与经纪商、DLL 授权及远程服务器通信相关的全部代码。策略仅针对单一品种与时间框架运行,依靠布林带与双指数移动平均线(DEMA)来捕捉趋势恢复。
原始 EA 支持最多六个货币对的独立参数配置,并会将优化结果上传至作者服务器。迁移版本聚焦于进出场流程,忽略所有外部依赖,以方便回测与进一步定制。
策略逻辑
- 数据订阅:订阅指定的 K 线类型(默认 1 小时),并绑定布林带与 DEMA 指标。
- 布林带反弹判定:所有条件基于最近一根完整收盘 K 线。
- 多头条件
- K 线开盘价位于前一根布林带下轨之下,但收盘价重新站上该下轨(假突破)。
- 当前 K 线的最低价高于前一根 K 线的最低价,同时前一根 K 线的最低价低于再前一根 K 线(双底结构)。
- DEMA 三个连续值呈上升状态(当前值 > 前一个值 > 再前一个值)。
- 空头条件
- K 线开盘价位于前一根布林带上轨之上,但收盘价跌回上轨之下(假突破)。
- 当前 K 线的最高价低于前一根 K 线的最高价,同时前一根 K 线的最高价高于再前一根 K 线(双顶结构)。
- DEMA 三个连续值呈下降状态。
- 多头条件
- 下单执行:在条件成立的下一根 K 线开盘时立即发送市价单。若启用
CloseOnSignal,则在反向信号出现时会先平掉原有仓位。 - 风控设置:通过
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(
RiskMax、LotSize、LotBalanceDivider)未移植,请在策略属性中设置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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import BollingerBands, DoubleExponentialMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan
class coensio_trader1_v06_strategy(Strategy):
def __init__(self):
super(coensio_trader1_v06_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 30)
self._bollinger_deviation = self.Param("BollingerDeviation", 1.5)
self._dema_period = self.Param("DemaPeriod", 20)
self._close_on_signal = self.Param("CloseOnSignal", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_open = None
self._prev_high = None
self._prev_low = None
self._prev_close = None
self._prev_upper_band = None
self._prev_lower_band = None
self._prev_dema = None
self._bollinger = None
self._dema = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(coensio_trader1_v06_strategy, self).OnStarted2(time)
self._bollinger = BollingerBands()
self._bollinger.Length = self._bollinger_period.Value
self._bollinger.Width = self._bollinger_deviation.Value
self._dema = DoubleExponentialMovingAverage()
self._dema.Length = self._dema_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ1 = CandleIndicatorValue(self._bollinger, candle)
civ1.IsFinal = True
bb_result = self._bollinger.Process(civ1)
civ2 = CandleIndicatorValue(self._dema, candle)
civ2.IsFinal = True
dema_result = self._dema.Process(civ2)
if bb_result.IsEmpty or dema_result.IsEmpty or not self._bollinger.IsFormed or not self._dema.IsFormed:
self._prev_open = float(candle.OpenPrice)
self._prev_close = float(candle.ClosePrice)
self._prev_low = float(candle.LowPrice)
self._prev_high = float(candle.HighPrice)
return
upper = float(bb_result.UpBand) if bb_result.UpBand is not None else 0.0
lower = float(bb_result.LowBand) if bb_result.LowBand is not None else 0.0
dema_value = float(dema_result)
if (self._prev_open is not None and self._prev_close is not None and
self._prev_low is not None and self._prev_high is not None and
self._prev_lower_band is not None and self._prev_upper_band is not None and
self._prev_dema is not None):
crossed_lower = self._prev_low <= self._prev_lower_band and self._prev_close > self._prev_lower_band
bullish_trend = dema_value > self._prev_dema
if crossed_lower and bullish_trend:
if self._close_on_signal.Value and self.Position < 0:
self.BuyMarket(abs(self.Position))
if self.Position <= 0:
self.BuyMarket()
crossed_upper = self._prev_high >= self._prev_upper_band and self._prev_close < self._prev_upper_band
bearish_trend = dema_value < self._prev_dema
if crossed_upper and bearish_trend:
if self._close_on_signal.Value and self.Position > 0:
self.SellMarket(self.Position)
if self.Position >= 0:
self.SellMarket()
self._prev_lower_band = lower
self._prev_upper_band = upper
self._prev_dema = dema_value
self._prev_open = float(candle.OpenPrice)
self._prev_close = float(candle.ClosePrice)
self._prev_low = float(candle.LowPrice)
self._prev_high = float(candle.HighPrice)
def OnReseted(self):
super(coensio_trader1_v06_strategy, self).OnReseted()
self._prev_open = None
self._prev_high = None
self._prev_low = None
self._prev_close = None
self._prev_upper_band = None
self._prev_lower_band = None
self._prev_dema = None
def CreateClone(self):
return coensio_trader1_v06_strategy()