在 GitHub 上查看

市场捕捉策略

概述

市场捕捉策略复刻了原始 MetaTrader 5 专家的思路。算法围绕一个随价格移动的中心价位构建动态网格,只要价格在中心附近来回摆动就建立对冲式的仓位。头寸分布在中心之上和之下,并设置固定的盈利目标;同时根据账户权益的里程碑来决定何时平掉亏损最重的仓位。

交易规则

  • 中心线:策略保存一个内部中心价位,从处理的第一根 K 线收盘价开始。当市场偏离中心超过设定的网格间距时,会按步长逐级移动中心。
  • 初始空单:为了与 MQL 脚本一致,可以在启动后立即开出一笔可选的空单。
  • 做多条件:当最新收盘价高于中心且上一根 K 线曾向下穿过中心时允许做多,同时检查附近是否已经存在多单,避免在同一价位重复建仓。
  • 做空条件:当最新收盘价低于中心且上一根 K 线曾向上穿过中心时允许做空,附近存在相同方向仓位时不会重复建仓。
  • 止盈机制:每笔交易都会记录一个目标价,该目标等于入场价加减若干个价格步长。当 K 线最高价(多单)或最低价(空单)触及目标时,以市价平仓。
  • 权益管理:策略持续监控投资组合权益。达到设定的盈利百分比后,会平掉部分浮亏最大的仓位锁定收益;达到设定的回撤百分比后,同样会减持亏损仓位以降低风险。每次触发阈值后都会重新计算新的权益基准。

参数

  • Enable Long / Enable Short:分别控制是否允许做多或做空。
  • Grid Steps:网格间距,以价格步长表示。
  • Take Profit Steps:止盈距离,以价格步长表示。
  • Open Initial Short:是否在启动时先行卖出一笔空单。
  • Use Equity Target:启用盈利阈值下的亏损仓位裁剪规则。
  • Track Drawdown:启用回撤阈值下的亏损仓位裁剪规则。
  • Equity Gain % / Equity Loss %:触发上述规则的权益变动百分比。
  • Loss Trades Up / Loss Trades Down:每次触发时最多关闭的亏损仓位数量。
  • Candle Type:用于决策过程的时间框架或自定义 K 线类型。
  • Volume(策略属性):每笔市价单的交易数量。

说明

  • 策略内部维护开仓记录,在 StockSharp 的净持仓模式下尽量模拟原脚本的对冲行为。
  • 距离相关的参数会乘以品种的价格步长,请确保标的提供有效的 PriceStep 数据。
  • 策略仅在 K 线收盘后做出决策,选择与目标交易周期相匹配的 K 线类型即可。
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>
/// Market capture strategy using EMA crossover to capture market direction changes.
/// </summary>
public class MarketCaptureStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal? _prevFast;
	private decimal? _prevSlow;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

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

		_fastPeriod = Param(nameof(FastPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicators");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

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

		_prevFast = null;
		_prevSlow = null;

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast == null || _prevSlow == null)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fast > slow;

		_prevFast = fast;
		_prevSlow = slow;

		if (!prevAbove && currAbove)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (prevAbove && !currAbove)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}
	}
}