在 GitHub 上查看

四小时摆动策略

概述

四小时摆动策略 将 MetaTrader 的 "4H swing" 智能交易系统迁移到 StockSharp 的高级 API。原始策略结合了趋势跟踪和多时间框架振荡指标。本实现同时订阅三个周期(入场周期、确认周期与宏观过滤周期),并使用 StockSharp 自带指标重建所有信号。

交易逻辑

  • 主趋势过滤采用三条基于典型价格的指数移动平均线。做多要求 Fast EMA > Medium EMA > Slow EMA,做空则相反。
  • 在确认周期计算随机指标,%K 必须位于 %D 之上才能做多,做空则需要 %K 位于 %D 之下。
  • Momentum 指标同样基于确认周期,将 StockSharp 的差值输出转换为 MetaTrader 风格的 100 比例。当最近三次读数中至少一次偏离 100 超过阈值时才允许开仓。
  • 宏观过滤使用 MACD。做多要求 MACD 线高于信号线,做空则反向。

当所有条件满足时,策略在下一根基础周期的收盘上发送市价单;如果已有反向仓位,新订单会先平仓再反转。

风险控制

  • 入场后立即设置固定的止损与止盈(以点数表示)。
  • TrailingStopPips 大于零时启用传统的跟踪止损。
  • 允许在达到设定利润后将止损移动到入场价附近,实现保本。
  • 可选的 UseMacdExit 会在 MACD 过滤方向翻转时平仓。

参数

参数 说明 默认值
TradeVolume 市价单默认数量。 0.01
CandleType 入场周期。 4H
SignalCandleType 用于随机指标和 Momentum 的确认周期。 7D (周线)
MacdCandleType MACD 宏观过滤周期。 30D
FastEmaPeriod, MediumEmaPeriod, SlowEmaPeriod 基于典型价格的 EMA 长度。 4, 14, 50
StochasticKPeriod, StochasticDPeriod, StochasticSmoothPeriod 随机指标设置。 13, 5, 5
MomentumPeriod Momentum 指标回溯长度。 14
MomentumThreshold 动量与 100 的最小偏离阈值。 0.3
StopLossPips, TakeProfitPips 止损与止盈的点数距离。 20, 50
TrailingStopPips 跟踪止损的点数(0 表示禁用)。 40
UseBreakEven 是否启用保本保护。 true
BreakEvenTriggerPips, BreakEvenOffsetPips 触发和偏移设置。 30, 30
UseMacdExit 当 MACD 方向反转时平仓。 false

说明

  • 为保持实现简洁,原始 EA 中的货币或权益锁定功能未移植。
  • 策略只处理闭合蜡烛,与 MetaTrader 的逐棒评估保持一致。
  • 所有 DataType 参数都可以按需调整,以适配其他时间框架组合。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class FourHourSwingStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public FourHourSwingStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}