在 GitHub 上查看

Color Trend CF 策略

该策略是 MQL 专家 Exp_ColorTrend_CF 的移植版本。策略使用两条指数移动平均线来识别趋势变化。快速 EMA 对价格变化反应灵敏,慢速 EMA 用于过滤趋势。当快速 EMA 上穿慢速 EMA 时开多仓;当快速 EMA 下穿慢速 EMA 时开空仓。

参数

  • Period – 快速 EMA 的基础周期,慢速 EMA 使用其两倍。
  • StopLoss – 以价格单位表示的止损距离。
  • TakeProfit – 以价格单位表示的止盈距离。
  • AllowBuyOpen – 允许开多仓。
  • AllowSellOpen – 允许开空仓。
  • AllowBuyClose – 允许在卖出信号时平多仓。
  • AllowSellClose – 允许在买入信号时平空仓。
  • CandleType – 指标计算使用的时间框架。

交易逻辑

  1. 订阅所选时间框架的K线。
  2. 计算快慢 EMA。
  3. 当快速 EMA 上穿慢速 EMA 时:
    • 如允许则平空仓。
    • 如允许则开多仓。
  4. 当快速 EMA 下穿慢速 EMA 时:
    • 如允许则平多仓。
    • 如允许则开空仓。
  5. 对已有仓位应用止损和止盈。

该实现使用 StockSharp 的高级 API 和指标绑定功能。

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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on crossing of two exponential moving averages.
/// Shorter EMA represents rising pressure while longer EMA acts as trend
/// filter.
/// </summary>
public class ColorTrendCfStrategy : Strategy {
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<bool> _allowBuyOpen;
	private readonly StrategyParam<bool> _allowSellOpen;
	private readonly StrategyParam<bool> _allowBuyClose;
	private readonly StrategyParam<bool> _allowSellClose;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _entryPrice;
	private bool _isLong;

	/// <summary>
	/// Base period for the fast EMA. Slow EMA uses double value.
	/// </summary>
	public int Period {
	get => _period.Value;
	set => _period.Value = value;
	}

	/// <summary>
	/// Stop loss distance in price units.
	/// </summary>
	public decimal StopLoss {
	get => _stopLoss.Value;
	set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance in price units.
	/// </summary>
	public decimal TakeProfit {
	get => _takeProfit.Value;
	set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Whether long entries are allowed.
	/// </summary>
	public bool AllowBuyOpen {
	get => _allowBuyOpen.Value;
	set => _allowBuyOpen.Value = value;
	}

	/// <summary>
	/// Whether short entries are allowed.
	/// </summary>
	public bool AllowSellOpen {
	get => _allowSellOpen.Value;
	set => _allowSellOpen.Value = value;
	}

	/// <summary>
	/// Whether closing long positions on sell signals is allowed.
	/// </summary>
	public bool AllowBuyClose {
	get => _allowBuyClose.Value;
	set => _allowBuyClose.Value = value;
	}

	/// <summary>
	/// Whether closing short positions on buy signals is allowed.
	/// </summary>
	public bool AllowSellClose {
	get => _allowSellClose.Value;
	set => _allowSellClose.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculation.
	/// </summary>
	public DataType CandleType {
	get => _candleType.Value;
	set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="ColorTrendCfStrategy"/>.
	/// </summary>
	public ColorTrendCfStrategy() {
	_period = Param(nameof(Period), 30)
			  .SetGreaterThanZero()
			  .SetDisplay("CF Period", "Base period for fast EMA",
				  "Indicator")
			  
			  .SetOptimize(10, 60, 10);

	_stopLoss =
		Param(nameof(StopLoss), 1000m)
		.SetDisplay("Stop Loss", "Stop loss in price units", "Risk")
		
		.SetOptimize(100m, 2000m, 100m);

	_takeProfit =
		Param(nameof(TakeProfit), 2000m)
		.SetDisplay("Take Profit", "Take profit in price units", "Risk")
		
		.SetOptimize(100m, 4000m, 100m);

	_allowBuyOpen = Param(nameof(AllowBuyOpen), true)
				.SetDisplay("Allow Buy", "Permission to open long",
					"Permissions");

	_allowSellOpen =
		Param(nameof(AllowSellOpen), true)
		.SetDisplay("Allow Sell", "Permission to open short",
				"Permissions");

	_allowBuyClose =
		Param(nameof(AllowBuyClose), true)
		.SetDisplay("Close Long", "Allow closing long positions",
				"Permissions");

	_allowSellClose =
		Param(nameof(AllowSellClose), true)
		.SetDisplay("Close Short", "Allow closing short positions",
				"Permissions");

		_candleType =
		Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe for indicator",
				"General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted() {
	base.OnReseted();
	_entryPrice = 0m;
	_isLong = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time) {
	base.OnStarted2(time);

	var fastEma = new EMA { Length = Period };
	var slowEma = new EMA { Length = Period * 2 };

	var subscription = SubscribeCandles(CandleType);
	var prevFast = 0m;
	var prevSlow = 0m;
	var initialized = false;

	subscription
		.Bind(fastEma, slowEma,
		  (candle, fast, slow) => {
			  if (candle.State != CandleStates.Finished)
			  return;

			  if (!IsFormedAndOnlineAndAllowTrading())
			  return;

			  if (!initialized) {
			  prevFast = fast;
			  prevSlow = slow;
			  initialized = true;
			  return;
			  }

			  var crossUp = prevFast <= prevSlow && fast > slow;
			  var crossDown = prevFast >= prevSlow && fast < slow;

			  prevFast = fast;
			  prevSlow = slow;

			  if (crossUp) {
			  if (AllowSellClose && Position < 0)
				  BuyMarket(Math.Abs(Position));

			  if (AllowBuyOpen && Position <= 0) {
				  _entryPrice = candle.ClosePrice;
				  _isLong = true;
				  BuyMarket(Volume + Math.Abs(Position));
			  }
			  } else if (crossDown) {
			  if (AllowBuyClose && Position > 0)
				  SellMarket(Position);

			  if (AllowSellOpen && Position >= 0) {
				  _entryPrice = candle.ClosePrice;
				  _isLong = false;
				  SellMarket(Volume + Math.Abs(Position));
			  }
			  }

			  if (_entryPrice != 0m) {
			  if (_isLong && Position > 0) {
				  var stop = _entryPrice - StopLoss;
				  var take = _entryPrice + TakeProfit;
				  if (candle.LowPrice <= stop ||
				  candle.HighPrice >= take)
				  SellMarket(Position);
			  } else if (!_isLong && Position < 0) {
				  var stop = _entryPrice + StopLoss;
				  var take = _entryPrice - TakeProfit;
				  if (candle.HighPrice >= stop ||
				  candle.LowPrice <= take)
				  BuyMarket(Math.Abs(Position));
			  }
			  }
		  })
		.Start();

	StartProtection(null, null);
	}
}