在 GitHub 上查看

A System Championship 策略

概述

  • 改写自 MetaTrader 4 专家顾问“A System: Championship Strategy Final Edit”(文件 ACB6.MQ4)。
  • 在可配置的主级别周期上寻找向上或向下突破,并通过最新买/卖报价确认动量。
  • 使用第二个周期重建原始 EA 中的多线程区间,用于计算移动止损的距离。
  • 保留原机器人中的全局权益止损、交易冷却时间与自适应仓位控制模块。

数据订阅

  • 订阅两个蜡烛序列(PrimaryTimeFrameSecondaryTimeFrame)来获取设置止盈与移动止损所需的高低点区间。
  • 订阅一级行情以便读取最新买卖价,这些价格驱动入场、止损/止盈触发以及回撤出场逻辑。

入场条件

  1. 等待主周期蜡烛收盘,并以 TakeFactor 乘以蜡烛区间得到目标距离。
  2. 多头信号:
    • 收盘价高于蜡烛中点;
    • 当前卖价突破蜡烛最高价;
    • 买价与蜡烛最低价之间的距离大于 MinStopDistance
  3. 空头信号为上述条件的镜像;
  4. 若计算得到的目标距离小于最小止损间距,则忽略信号。

出场管理

  • 初始保护:止损锚定在前一根蜡烛的低点/高点,止盈为入场价加减 TakeFactor 倍的区间。
  • 回撤出场FallLimit / FallFactor):
    • 持续跟踪持仓的最大有利波动。
    • 当当前波动小于 FallLimit * maxMove 且波动已超过 FallFactor * target 时,立即市价平仓。
  • 移动止损TrailFactor):
    • 距离等于次级周期蜡烛区间乘以 TrailFactor
    • 止损只会朝盈利方向推进,且不会跨越止盈或最小止损间距。
  • 硬性止损/止盈:当价格触及当前维护的止损或止盈时,立刻用市价单离场。

风险管理

  • 动态仓位:结合 RiskPerTrade 与证券的点值(StepSizeStepPrice)计算风险仓位,并按照交易所最小手数与步长取整,不会低于 BaseVolume
  • 统计调整:利用原始 EA 中的 LossesExpected/TradesExpected 比例与实际亏损比对,动态放大或缩小每笔风险。
  • 权益止损SystemStop):跟踪权益峰值,当当前权益低于 SystemStop * 峰值 时禁止新开仓,同时输出日志提示启停情况。
  • 交易冷却TradePause):每次市价委托执行后启动冷却窗口,与 MT4 版本保持一致。

参数

名称 默认值 说明
PrimaryTimeFrame 1 天 用于识别突破的主周期。
SecondaryTimeFrame 4 小时 用于计算移动止损距离的周期。
TakeFactor 0.8 主周期区间乘数,用于生成止盈。
TrailFactor 10 次级周期区间乘数,用于移动止损。
FallLimit 0.5 允许触发回撤出场的最大利润比例。
FallFactor 0.4 必须达到的目标比例,才允许回撤离场。
RiskPerTrade 0.02 每笔交易的基础风险占权益比例。
BaseVolume 1 当风险计算结果低于最小手数时使用的兜底仓位。
MinStopDistance 0 交易所要求的最小止损距离(价格单位)。
TradePause 5 分钟 每次下单后的等待时间。
SystemStop 0.8 权益止损系数,例如 0.8 代表允许 20% 回撤。
LossesExpected 20 预期亏损交易数量,用于风险调节。
TradesExpected 85 预期总交易数量,用于风险调节。

实现说明

  • 原版的锁单/对冲线程未移植,因为 StockSharp 策略以净头寸运行;风险控制与移动止损已经提供等效的资金保护。
  • 为兼容回测器,止损与止盈在策略内部跟踪,而不是发送独立的委托。
  • 请确保证券提供 StepSizeStepPriceMinVolumeVolumeStep 信息,否则仓位会退化为 BaseVolume
  • 运行时务必订阅实时报价,否则仅靠蜡烛驱动会导致止损响应存在一个周期延迟。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Multi-timeframe breakout strategy converted from the original MetaTrader "A System" expert advisor.
/// Enters on momentum breakouts when close is above/below the midpoint of the previous candle range.
/// Uses ATR-based trailing stop for position management.
/// </summary>
public class ASystemChampionshipStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _takeFactor;
	private readonly StrategyParam<decimal> _trailFactor;

	private decimal _prevHigh;
	private decimal _prevLow;
	private decimal _prevClose;
	private bool _hasPrev;
	private decimal _entryPrice;
	private decimal _stopPrice;

	public ASystemChampionshipStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for breakout detection.", "General");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "Period for ATR used in trailing stop.", "Indicators");

		_takeFactor = Param(nameof(TakeFactor), 2.5m)
			.SetDisplay("Take Factor", "ATR multiplier for take profit.", "Risk");

		_trailFactor = Param(nameof(TrailFactor), 1.5m)
			.SetDisplay("Trail Factor", "ATR multiplier for trailing stop.", "Risk");
	}

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

	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	public decimal TakeFactor
	{
		get => _takeFactor.Value;
		set => _takeFactor.Value = value;
	}

	public decimal TrailFactor
	{
		get => _trailFactor.Value;
		set => _trailFactor.Value = value;
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevHigh = 0;
		_prevLow = 0;
		_prevClose = 0;
		_hasPrev = false;
		_entryPrice = 0;
		_stopPrice = 0;
	}

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

		_prevHigh = 0;
		_prevLow = 0;
		_prevClose = 0;
		_hasPrev = false;
		_entryPrice = 0;
		_stopPrice = 0;

		var atr = new AverageTrueRange { Length = AtrPeriod };

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

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

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

		if (atrValue <= 0)
		{
			SaveCandle(candle);
			return;
		}

		// Manage open position
		if (Position > 0)
		{
			// Trail stop up
			var newStop = candle.ClosePrice - atrValue * TrailFactor;
			if (newStop > _stopPrice)
				_stopPrice = newStop;

			if (candle.LowPrice <= _stopPrice)
			{
				SellMarket();
				ResetPosition();
			}
			else if (_entryPrice > 0 && candle.HighPrice >= _entryPrice + atrValue * TakeFactor)
			{
				SellMarket();
				ResetPosition();
			}
		}
		else if (Position < 0)
		{
			// Trail stop down
			var newStop = candle.ClosePrice + atrValue * TrailFactor;
			if (_stopPrice == 0 || newStop < _stopPrice)
				_stopPrice = newStop;

			if (candle.HighPrice >= _stopPrice)
			{
				BuyMarket();
				ResetPosition();
			}
			else if (_entryPrice > 0 && candle.LowPrice <= _entryPrice - atrValue * TakeFactor)
			{
				BuyMarket();
				ResetPosition();
			}
		}

		// Entry logic
		if (_hasPrev && Position == 0)
		{
			var mid = (_prevHigh + _prevLow) / 2m;

			if (_prevClose > mid && candle.ClosePrice > _prevHigh)
			{
				// Bullish breakout
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = candle.ClosePrice - atrValue * TrailFactor;
			}
			else if (_prevClose < mid && candle.ClosePrice < _prevLow)
			{
				// Bearish breakout
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = candle.ClosePrice + atrValue * TrailFactor;
			}
		}

		SaveCandle(candle);
	}

	private void SaveCandle(ICandleMessage candle)
	{
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_prevClose = candle.ClosePrice;
		_hasPrev = true;
	}

	private void ResetPosition()
	{
		_entryPrice = 0;
		_stopPrice = 0;
	}
}