在 GitHub 上查看

N Candles v3 策略

概述

该策略会监控最近收盘的蜡烛线,只要连续 N 根蜡烛具有相同方向(全部阳线或全部阴线),就顺势开仓。同时它限制同向持仓的数量,避免超过 Max Positions 指定的上限。本实现将 MetaTrader 5 平台上的专家顾问迁移到了 StockSharp 的高级 API。

交易逻辑

  • 订阅配置的蜡烛类型,只处理已经收盘的蜡烛。
  • 对每根蜡烛判断实体方向:收盘价高于开盘价视为阳线,低于开盘价视为阴线,相等则视为十字星。
  • 出现十字星时重置计数;否则只要当前蜡烛方向与前一根相同就继续累加。当计数达到 Identical Candles 参数时发出新的交易信号。
  • 多头信号会先平掉现有空头,再在不超过 Max Positions * Volume 的前提下加仓多头。
  • 空头信号与其对称运作,用于连续阴线的情况。

风险控制

  • 每次成交后都会根据当前平均持仓价重新下达止损和止盈委托。
  • 止盈与止损距离都以品种的 PriceStep 为单位:Take Profit PointsStop Loss Points 分别表示需要乘以多少个最小价格步长。
  • 当价格朝持仓方向运行超过 Trailing Stop Points 指定的距离时,启用阶梯式跟踪止损;只有当价格比上一档跟踪价格进一步推进 Trailing Step Points 时才会上移/下移保护价位。

参数

  • Candle Type – 要分析的时间框架或蜡烛源。
  • Identical Candles – 触发信号所需的连续同向蜡烛数量。
  • Volume – 每次加仓的下单数量,单位为标的资产数量。
  • Max Positions – 同一方向允许持有的最大加仓次数。
  • Take Profit Points – 止盈距离,按最小价格步长的倍数表示。
  • Stop Loss Points – 止损距离,按最小价格步长的倍数表示。
  • Trailing Stop Points – 启用跟踪止损时与价格之间保持的基础距离,为 0 时关闭跟踪止损。
  • Trailing Step Points – 每次移动跟踪止损前所需的额外价格推进距离。

其他说明

  • 策略以净头寸方式工作:出现反向信号时会先平掉旧仓,再建立新的方向。
  • 每次成交都会重新创建保护性委托,使其数量与当前持仓保持一致。
  • 若合约没有提供有效的 PriceStep,策略会默认使用 1 作为价格步长。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that opens positions after detecting a sequence of candles with the same direction.
/// Uses StartProtection for take profit and stop loss management.
/// </summary>
public class NCandlesV3Strategy : Strategy
{
	private readonly StrategyParam<int> _identicalCandles;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<DataType> _candleType;

	private int _sequenceDirection;
	private int _sequenceCount;

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

	/// <summary>
	/// Number of consecutive candles required before entering a trade.
	/// </summary>
	public int IdenticalCandles
	{
		get => _identicalCandles.Value;
		set => _identicalCandles.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price steps.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public NCandlesV3Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles analysed by the strategy", "General");

		_identicalCandles = Param(nameof(IdenticalCandles), 3)
			.SetRange(1, 10)
			.SetDisplay("Identical Candles", "Required number of equal candles", "Pattern");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
			.SetRange(0m, 500m)
			.SetDisplay("Take Profit Points", "Take profit distance in price steps", "Risk Management");

		_stopLossPoints = Param(nameof(StopLossPoints), 50m)
			.SetRange(0m, 500m)
			.SetDisplay("Stop Loss Points", "Stop loss distance in price steps", "Risk Management");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_sequenceDirection = 0;
		_sequenceCount = 0;
	}

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

		_sequenceDirection = 0;
		_sequenceCount = 0;

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

		var step = Security?.PriceStep ?? 1m;
		var takeProfit = TakeProfitPoints > 0 ? TakeProfitPoints * step : (decimal?)null;
		var stopLoss = StopLossPoints > 0 ? StopLossPoints * step : (decimal?)null;

		if (takeProfit != null || stopLoss != null)
			StartProtection(takeProfit ?? 0, stopLoss ?? 0);

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

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

		var direction = GetDirection(candle);
		UpdateSequence(direction);

		if (_sequenceCount < IdenticalCandles)
			return;

		if (_sequenceDirection > 0 && Position <= 0)
		{
			BuyMarket();
		}
		else if (_sequenceDirection < 0 && Position >= 0)
		{
			SellMarket();
		}
	}

	private static int GetDirection(ICandleMessage candle)
	{
		if (candle.ClosePrice > candle.OpenPrice)
			return 1;
		if (candle.ClosePrice < candle.OpenPrice)
			return -1;
		return 0;
	}

	private void UpdateSequence(int direction)
	{
		if (direction == 0)
		{
			_sequenceDirection = 0;
			_sequenceCount = 0;
			return;
		}

		if (_sequenceDirection == direction)
		{
			_sequenceCount++;
		}
		else
		{
			_sequenceDirection = direction;
			_sequenceCount = 1;
		}
	}
}