在 GitHub 上查看

单均线通道突破策略

概述

单均线通道突破策略 在 StockSharp 高级 API 中重现 MetaTrader 5 专家顾问 One MA EA 的逻辑。策略绘制一条带有水平位移的移动平均线,并在其上下构建以“点”(pip)为单位的通道。当价格在同一根 K 线内先触及通道,再以开盘价突破通道外时,系统按照突破方向建立仓位,同时通过可选的止损和止盈保护自动管理风险。

主要特性:

  • 支持多种均线计算方法(SMA、EMA、SMMA、LWMA)。
  • 可选择输入到均线的价格类型(收盘价、开盘价、最高价、最低价、中位价、典型价、加权价)。
  • 提供独立的均线读取位移和价格读取位移,完全对应原 EA 中的 Current Bar 设置。
  • 使用标的证券的 PriceStep 与小数位数自动把点值转换为实际价格增量(3/5 位小数的外汇品种自动映射为经典 pip)。

交易逻辑

  1. 指标准备

    • 按照 MaPeriodMaMethodParamMaShiftAppliedPriceType 计算移动平均线,使用订阅的 CandleType K 线数据。
    • ChannelHighPipsChannelLowPips 转换为价格增量,在均线附近形成上下通道。
    • 通过固定长度的历史缓冲区访问过去的均线值与 K 线数据(MaBarShiftPriceBarShift),以复制原始 EA 的行为。
  2. 信号判定

    • 看涨突破:目标 K 线的最低价位于均线与上轨之间,同时开盘价高于上轨,且当前无多头仓位 (Position <= 0),则买入。
    • 看跌突破:目标 K 线的最高价位于均线与下轨之间,同时开盘价低于下轨,且当前无空头仓位 (Position >= 0),则卖出。
    • 委托数量等于设定的 TradeVolume 加上平掉反向仓位所需的数量,模拟原专家顾问在对冲账户中的行为。
  3. 风险控制

    • StopLossPipsTakeProfitPips 转换为绝对价格距离后传递给 StartProtection,为每笔仓位自动挂出止损/止盈单。
    • 参数为零时关闭对应的保护单。

策略不会额外定义离场逻辑,仓位仅通过保护单或反向信号反转平仓。

参数说明

参数 说明
MaPeriod 移动平均线周期,必须大于 0。
MaShift 均线的水平位移(单位:K 线数量)。正值向右偏移。
MaMethodParam 均线类型(SmaEmaSmmaLwma)。
AppliedPriceType 计算均线所用的价格类型。
MaBarShift 读取第几根历史均线值(0 表示当前处理的 K 线)。
PriceBarShift 读取第几根历史 K 线进行信号判定。
ChannelHighPips 上通道相对均线的距离(pip)。
ChannelLowPips 下通道相对均线的距离(pip)。
StopLossPips 止损距离(pip),0 表示不启用。
TakeProfitPips 止盈距离(pip),0 表示不启用。
TradeVolume 下单数量,对应 Strategy.Volume
CandleType 用于计算与信号判定的 K 线类型/周期。

实现细节

  • Pip 转换:当证券的小数位为 3 或 5 时,pip 大小 = PriceStep * 10;否则为 PriceStep
  • 历史数据通过固定长度滑动窗口保存,避免直接调用指标 GetValue 方法,符合项目规范。
  • 只处理已完成的 K 线,忽略未完成的数据以避免虚假信号。
  • 若主程序提供图表区域,策略会绘制价格 K 线与成交记录,便于回测与监控。

使用建议

  • 确认标的证券提供有效的 PriceStepDecimals 信息;否则需手动调整 pip 参数。
  • 根据市场与周期优化 MaPeriod、通道距离以及历史位移参数,以适配不同品种。
  • 实盘部署时建议结合组合级别风控,因为策略在任何时刻仅持有单方向净头寸。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// One MA channel breakout strategy.
/// Places an SMA channel around price with configurable offset, trades breakouts.
/// </summary>
public class OneMaChannelBreakoutStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<decimal> _channelOffset;

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

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	public decimal ChannelOffset
	{
		get => _channelOffset.Value;
		set => _channelOffset.Value = value;
	}

	public OneMaChannelBreakoutStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_maPeriod = Param(nameof(MaPeriod), 44)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Moving average length", "Indicators");

		_channelOffset = Param(nameof(ChannelOffset), 0.005m)
			.SetGreaterThanZero()
			.SetDisplay("Channel Offset", "Percentage offset for channel", "Indicators");
	}

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

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

		var sma = new SimpleMovingAverage { Length = MaPeriod };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var upper = maValue * (1 + ChannelOffset);
		var lower = maValue * (1 - ChannelOffset);
		var close = candle.ClosePrice;

		// Bullish breakout above upper channel
		if (close > upper && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Bearish breakdown below lower channel
		else if (close < lower && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
	}
}