在 GitHub 上查看

Plan X 策略

概述

Plan X 策略是从原始的 MetaTrader 5 智能交易系统移植的突破型模型。策略对每根已完成 K 线的收盘价进行检测,与向后偏移的参考 K 线进行比较。当最新收盘价较参考值突破指定的通道高度时,将按突破方向开仓;启用反向模式后则会按相反方向交易。

本实现基于 StockSharp 高级 API,支持可配置的止损、止盈、移动止损以及交易时间过滤器。

工作流程

  1. K 线处理:订阅指定类型的 K 线,只处理已经收盘的 K 线,并保存短期收盘价序列用于比较。
  2. 突破检测:若最新收盘价高于参考收盘价加上通道高度,生成做多信号;若低于参考收盘价减去通道高度,生成做空信号;启用反向时信号方向调换。
  3. 下单执行:使用市价单入场。若当前持有反向仓位,将自动包含绝对仓位量,以一次市价单完成平仓并反向建仓。
  4. 风控管理:入场后立即设置止损和止盈。当浮动利润超过“移动止损距离 + 步长”时,移动止损会跟随价格推进。
  5. 时间控制:可设定允许交易的开始与结束小时;若开始小时大于结束小时,则视为跨越午夜的交易窗口。

参数说明

参数 说明
Stop Loss (pips) 止损距离(点),根据品种最小报价单位转换为价格。
Take Profit (pips) 止盈距离(点)。
Trailing Stop (pips) 移动止损距离(点),为零时禁用移动止损。
Trailing Step (pips) 每次调整移动止损所需的额外利润(点)。启用移动止损时必须大于零。
Channel Height (pips) 触发突破所需的通道高度(点)。
Candle Shift 最新 K 线与参考 K 线之间的偏移数量。
Use Time Control 是否启用交易时段过滤。
Start Hour 允许交易的起始小时(0–23)。
End Hour 允许交易的结束小时(0–23)。
Reverse Signals 反向执行突破信号。
Order Volume 下单数量(手数或合约数)。
Candle Type 用于计算的 K 线数据类型。

信号逻辑

  • 做多:最新收盘价 ≥ 参考收盘价 + 通道高度,且未启用反向。
  • 做空:最新收盘价 ≤ 参考收盘价 − 通道高度,且未启用反向。
  • 启用反向时,做多与做空条件互换。

移动止损

  • 当浮动利润超过 Trailing Stop + Trailing Step(价格单位)时启动。
  • 做多仓位时,新的止损价更新为 最高价 − Trailing Stop,且必须高于当前止损。
  • 做空仓位时,新的止损价更新为 最低价 + Trailing Stop,且必须低于当前止损。

其他说明

  • 点值计算与 MQL 版本一致,对于 3/5 位小数报价会将价格步长乘以 10。
  • 即使处于禁止交易的时段,也会持续管理已有仓位。
  • 启动时调用 StartProtection() 以启用平台的保护机制。
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 PlanXStrategy : 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 PlanXStrategy()
	{
		_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;
	}
}