在 GitHub 上查看

Arttrader 策略

概述

Arttrader 是 MetaTrader 4 顾问 Arttrader_v1_5 的移植版本。策略基于小时级别 K 线运行,通过计算开盘价的指数移动平均线(EMA)斜率来捕捉平稳的方向性行情。只有在满足 EMA 斜率与蜡烛内部位置条件时才允许入场,而专门的波动率防护在出现大幅跳空后会阻止交易。仓位管理由延时止损、硬性紧急止损与固定止盈以及基于成交量的保护机制共同完成。

该 StockSharp 实现保留了原始输入参数,并使用高层的市价指令执行。所有计算均基于已完成的 K 线;原策略中的分钟延迟通过比较设定值与当前 K 线持续时间来近似实现。

策略逻辑

指标

  • 开盘价 EMA —— 对开盘价计算的单条 EMA,可配置周期;当前值与前一值之间的差值(以点为单位)定义了斜率。

过滤条件

  • 斜率范围 —— EMA 斜率必须介于 Slope MinSlope Max 之间,过弱或过强的趋势都会被过滤。
  • 蜡烛位置 —— 多头要求收盘价不高于开盘价,并且位于最低价附近(考虑 Entry Slip 的容差);空头条件相反,要求靠近最高价。Entry DelayExit Delay 通过比较 K 线持续时间与设定分钟数来判断是否满足。
  • 跳空检测 —— 检查最近五根蜡烛的开盘价差异。如果任意单根跳空超过 Big Jump 点,或任意两根组合的跳空超过 Double Jump 点,则当前蜡烛不再产生信号。

入场

  • 多头入场 —— 当所有过滤条件满足、EMA 斜率为正且当前无持仓时触发。保存的合成入场价会按 Spread Adjust 参数调整,以模拟原策略的点差补偿。
  • 空头入场 —— 逻辑对称,需要 EMA 斜率为负且当前无持仓。

出场

  • 延时智能止损 —— 仅在满足 Exit Delay 后才会评估智能止损。对于多头,收盘价必须高于开盘价且接近最高价,同时相对合成入场价的亏损点数要超过 Smart Stop
  • 成交量保护 —— 如果上一根蜡烛的成交量小于或等于 Min Volume,则下一根蜡烛立即平仓。
  • 紧急止损 / 止盈 —— 入场后立即记录紧急止损与固定止盈价格。一旦蜡烛价格区间触及任意水平,仓位立即关闭,无需等待延时逻辑。

参数

  • Order Volume – 市价订单的交易手数。
  • EMA Period – 开盘价 EMA 的周期。
  • Big Jump (pips) – 允许的最大单根跳空幅度。
  • Double Jump (pips) – 允许的最大两根组合跳空幅度。
  • Smart Stop (pips) – 触发延时止损所需的点数距离。
  • Emergency Stop (pips) – 硬性止损距离,每根蜡烛都会检查。
  • Take Profit (pips) – 固定止盈距离,每根蜡烛都会检查。
  • Slope Min / Slope Max (pips) – 允许交易的 EMA 斜率范围。
  • Entry Delay (min) – 允许入场前所需的最小蜡烛持续时间(分钟)。
  • Exit Delay (min) – 允许执行延时止损前所需的最小蜡烛持续时间(分钟)。
  • Entry Slip / Exit Slip (pips) – 验证入场和出场条件时,收盘价与极值之间的容差。
  • Min Volume – 上一根蜡烛的最小成交量阈值,若未超过则平仓。
  • Spread Adjust (pips) – 对合成入场价的点差修正。
  • Slippage (pips) – 为兼容 MT4 保留的滑点参数,当前实现仅供记录。
  • Candle Type – 参与计算的蜡烛类型(默认 1 小时)。

备注

  • StockSharp 版本使用 BuyMarket/SellMarket 进行开仓和平仓,与原 EA 的单一持仓模式一致。
  • 由于依赖已完成的蜡烛,原策略中的分钟级判断通过比较设定值与实际蜡烛持续时间来近似实现。
  • 紧急止损和止盈通过比较蜡烛的最高价与最低价来触发,从而模拟 MT4 中的经纪商侧保护单。
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>
/// Arttrader strategy - EMA slope trend follower.
/// Buys when EMA is rising and price crosses above it.
/// Sells when EMA is falling and price crosses below it.
/// </summary>
public class ArttraderStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevEma;
	private decimal _prevPrevEma;
	private decimal _prevClose;
	private bool _hasPrev;

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

	public ArttraderStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 14)
			.SetDisplay("EMA Period", "EMA period", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevEma = 0m;
		_prevPrevEma = 0m;
		_prevClose = 0m;
		_hasPrev = false;
	}

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

		_hasPrev = false;

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };

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

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

		var close = candle.ClosePrice;

		if (!_hasPrev)
		{
			_prevPrevEma = emaValue;
			_prevEma = emaValue;
			_prevClose = close;
			_hasPrev = true;
			return;
		}

		var emaRising = emaValue > _prevEma;
		var emaFalling = emaValue < _prevEma;

		// Price crosses above EMA when it is rising - buy
		if (emaRising && _prevClose <= _prevEma && close > emaValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Price crosses below EMA when it is falling - sell
		else if (emaFalling && _prevClose >= _prevEma && close < emaValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevPrevEma = _prevEma;
		_prevEma = emaValue;
		_prevClose = close;
	}
}