在 GitHub 上查看

Expert Candles 策略

概述

Expert Candles 策略 是 MetaTrader 5 Expert_Candles 智能交易系统的 StockSharp 移植版本。策略在每根 K 线收盘时 检查是否出现长影线的反转形态,一旦识别到看涨或看跌的组合蜡烛,就在对应方向开仓,并按照原版 EA 的逻辑应用 资金管理规则。

实现完全基于 StockSharp 的高级 API:通过蜡烛订阅聚合历史数据,使用市价单开平仓,并在策略内部维护止损/止盈 水位。

交易逻辑

  1. 每当收到一根完成的蜡烛时,策略会把它与最多 Range 根更早的蜡烛合并,直到合成蜡烛的总高度超过 MinimumPoints(先按合约的点值转换为价格单位)。
  2. 当合成蜡烛的上影线较短(小于 ShadowSmall)且下影线显著较长(大于 ShadowBig)时触发看涨信号; 如果下影线短而上影线长,则触发看跌信号。
  3. 入场价在蜡烛收盘价的基础上偏移 LimitFactor * rangeSize。正值会把成交价拉回蜡烛内部,对应原版的限价委托。
  4. 止损和止盈距离分别等于 StopLossFactorTakeProfitFactor 乘以合成蜡烛的高度。当后续蜡烛的最高价或最低价 触碰到这些水位时,立即平仓。
  5. 信号在 ExpirationBars 根完整蜡烛内有效,超过时间窗后必须等待新的形态。
  6. 新信号出现前会先平掉相反方向的持仓,从而复刻 MQL5 引擎的行为。

资金管理

  • FixedVolume 为默认下单手数。
  • 当设置了止损且 RiskPercent 大于 0 时,策略会按账户权益的百分比来计算下单数量。止损距离会结合 Security.PriceStepSecurity.StepPrice 转换成货币金额。
  • 如果交易所提供 VolumeStep 元数据,最终手数会对齐到可交易的步长。

参数

参数 默认值 说明
CandleType H1 请求蜡烛数据所用的时间框架。
Range 3 允许合并的最大香蕉蜡烛数量。
MinimumPoints 50 合成蜡烛所需的最小高度(按 PriceStep 换算)。
ShadowBig 0.5 主影线的最小比例阈值。
ShadowSmall 0.2 次要影线的最大比例阈值。
LimitFactor 0.0 入场价相对合成高度的偏移比例(正值代表向蜡烛内部偏移)。
StopLossFactor 2.0 止损距离占合成高度的倍数,设为 0 表示禁用。
TakeProfitFactor 1.0 止盈距离占合成高度的倍数,设为 0 表示禁用。
ExpirationBars 4 信号保持有效的完整蜡烛数量。
FixedVolume 0.1 无法计算风险仓位时使用的兜底手数。
RiskPercent 10 有止损时每笔交易承担的权益百分比。

使用说明

  • 策略需要准确的 PriceStepStepPriceVolumeStep 信息才能还原 MetaTrader 的点值计算。如无这些数据,请适当 调整参数。
  • 只有 CandleStates.Finished 的蜡烛才会触发信号,请确保数据源会推送完整蜡烛事件。
  • 止损与止盈由策略内部模拟:当完成蜡烛的高/低价越过目标价位时立即平仓。
  • 内部仅保留最近 500 根蜡烛,以控制内存占用。

与原版 EA 的差异

  • 移植版使用市价单入场,LimitFactor 会把成交价调整到原本的限价位置。
  • 资金管理可选,RiskPercent 设为 0 时退回原始的固定手数模式。
  • 止损/止盈逻辑直接写在策略里,不再依赖外部的跟踪模块。
namespace StockSharp.Samples.Strategies;

using System;

using Ecng.Common;

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

/// <summary>
/// Candlestick reversal strategy: detects hammer/shooting star patterns.
/// Buys on bullish hammer candle, sells on bearish shooting star.
/// </summary>
public class ExpertCandlesStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _shadowRatio;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public decimal ShadowRatio
	{
		get => _shadowRatio.Value;
		set => _shadowRatio.Value = value;
	}

	public ExpertCandlesStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_shadowRatio = Param(nameof(ShadowRatio), 0.3m)
			.SetGreaterThanZero()
			.SetDisplay("Shadow Ratio", "Min shadow to body ratio for pattern", "Signals");
	}

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

		var sma = new SimpleMovingAverage { Length = 20 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, (candle, smaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var open = candle.OpenPrice;
				var high = candle.HighPrice;
				var low = candle.LowPrice;
				var close = candle.ClosePrice;
				var range = high - low;

				if (range <= 0)
					return;

				var body = Math.Abs(close - open);
				var upperShadow = high - Math.Max(open, close);
				var lowerShadow = Math.Min(open, close) - low;

				var isHammer = lowerShadow > range * ShadowRatio && upperShadow < body;
				var isShootingStar = upperShadow > range * ShadowRatio && lowerShadow < body;

				if (isHammer && close > smaVal && Position <= 0)
					BuyMarket();
				else if (isShootingStar && close < smaVal && Position >= 0)
					SellMarket();
			})
			.Start();

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