在 GitHub 上查看

Arttrader v1.5 策略

概述

Arttrader v1.5 策略来源于 MetaTrader 5 平台的同名专家顾问,属于趋势跟随型系统。它使用较高周期的指数移动平均线(EMA)斜率作为方向滤波,同时结合短周期 K 线的价格形态与时间过滤器。移植到 StockSharp 后,策略保留了原始版本的风险控制行为,包括对异常跳空的严格检查、进出场时间窗口以及基于价格距离的紧急离场机制。

策略同时订阅两组 K 线数据:

  • 交易周期(默认 5 分钟)负责触发进场、出场及所有价格过滤条件。
  • 趋势周期(默认 1 小时)提供 EMA 的输入,用于计算更高周期的趋势斜率。

策略以净仓方式交易单一标的。当出现反向信号时,会先平掉已有仓位,再按信号方向提交新的市价单。

信号规则

  1. EMA 斜率过滤
    • 使用趋势周期的 K 线开盘价计算 EMA,并将最近两个值的差作为斜率。
    • 多头信号要求斜率在 SlopeSmallSlopeLarge(点值换算后)之间且为正;空头信号要求斜率在相同区间内但为负。
  2. 小时内时间窗口
    • 只有当当前小时已经过去 MinutesBegin 分钟后才会评估信号,复现 MT5 中 TimeCurrent() 的判断。
  3. 价格形态确认
    • 多头需要当前 K 线收盘价靠近最低价并且收盘价不高于开盘价,SlipBegin 决定允许的偏差。
    • 空头需要当前 K 线收盘价靠近最高价并且收盘价不低于开盘价。
  4. 跳空过滤
    • 在最近 6 根 K 线中,只要任意两根相邻开盘价的差值超过 BigJump,就会取消多空信号。
    • 若隔一根的开盘价差值超过 DoubleJump,同样会取消信号,以规避剧烈波动阶段。

离场规则

  1. 延时智能止损
    • 建仓时记录一个参考价,并根据 Adjust 模拟点差偏移。
    • 当收盘价相对参考价的浮亏达到 StopLoss 时,策略会等待当前小时经过 MinutesEnd 分钟且 K 线出现回撤形态(符合 SlipEnd 条件)后,以市价平仓。
  2. 紧急止损
    • 如果 K 线的最高或最低触及距离入场价 EmergencyLoss 的水平,立即执行离场,模拟原始 EA 的硬止损。
  3. 止盈
    • 价格达到 TakeProfit 所设距离时立即平仓。
  4. 成交量保护
    • 若上一根 K 线总成交量不大于 MinVolume,视为流动性不足,当前持仓会被强制平仓。

参数说明

参数 默认值 说明
Volume 1 市价单的下单手数,反向开仓时会自动加上已有仓位的绝对值。
EmaPeriod 11 趋势周期 EMA 的计算长度(使用开盘价)。
BigJump 30 相邻两根 K 线开盘价允许的最大跳空,按合约最小变动价位换算。
DoubleJump 55 间隔一根 K 线的开盘价允许的最大跳空。
StopLoss 20 触发延时止损逻辑所需的最小浮亏。
EmergencyLoss 50 紧急止损触发距离,达到后立即平仓。
TakeProfit 25 固定止盈目标距离。
SlopeSmall 5 允许进场的最小 EMA 斜率绝对值。
SlopeLarge 8 允许进场的最大 EMA 斜率绝对值。
MinutesBegin 25 自小时开始后的等待分钟数,在此之前不产生新信号。
MinutesEnd 25 自小时开始后的等待分钟数,在此之前不执行延时止损出场。
SlipBegin 0 进场确认时收盘价距离高低点的最大偏差。
SlipEnd 0 延时止损确认时收盘价距离高低点的最大偏差。
MinVolume 0 上一根 K 线所需的最小成交量,低于该值会强制平仓。
Adjust 1 保存内部参考入场价时的偏移量,用于模拟点差。
CandleType 5 分钟 交易信号使用的 K 线类型。
TrendCandleType 1 小时 供 EMA 斜率过滤使用的 K 线类型。

所有基于价格的参数都会乘以合约的最小变动价位。如果合约精度为 3 或 5 位小数,代码会额外乘以 10,以模拟 MT5 中的点 (pip) 处理逻辑。

实现细节

  • BuyMarketSellMarket 会在需要时追加反向仓位的绝对值,确保仓位切换时先平后开。
  • 只有当交易周期与趋势周期不同才会创建两条订阅,若参数相同则复用单一订阅同时驱动 EMA 与交易逻辑。
  • 紧急止损与止盈在策略内部判断触发,因为 StockSharp 不会自动为市价单附带保护单。
  • 全程使用高阶 API(Bind 订阅、StartProtection、图表工具),保持代码风格与仓库约定一致。

使用建议

  • 可根据交易品种的交易时段调整 MinutesBeginMinutesEnd,默认配置适用于外汇等小时节奏明显的市场。
  • 在流动性容易突然下降的品种(例如部分大宗商品)上,可提高 MinVolume 以避免在冷清时段持仓。
  • 跳空过滤仅检查最近 6 根 K 线,若交易周期过长过滤效果会减弱,应适当缩短周期或增大参数。
  • 调整参数前请确认合约的最小变动价位,确保 BigJumpStopLoss 等值与实际报价单位对应。
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;

/// <summary>
/// Arttrader v1.5 strategy (simplified).
/// Uses EMA slope on a higher timeframe to filter entries,
/// with candle pattern confirmation on the trading timeframe.
/// </summary>
public class ArttraderV15Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<DataType> _trendCandleType;
	private readonly StrategyParam<int> _emaPeriod;

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

	public DataType TrendCandleType
	{
		get => _trendCandleType.Value;
		set => _trendCandleType.Value = value;
	}

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

	public ArttraderV15Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Trading candles", "General");

		_trendCandleType = Param(nameof(TrendCandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Trend Candle Type", "Trend candles for EMA", "General");

		_emaPeriod = Param(nameof(EmaPeriod), 11)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period on trend timeframe", "Indicators");
	}

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

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		decimal currentEma = 0;
		decimal previousEma = 0;
		bool hasCurrentEma = false;
		bool hasPreviousEma = false;

		// Subscribe to trend timeframe for EMA slope
		var trendSub = SubscribeCandles(TrendCandleType);
		trendSub
			.Bind(ema, (ICandleMessage candle, decimal emaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (hasCurrentEma)
				{
					previousEma = currentEma;
					hasPreviousEma = true;
				}

				currentEma = emaVal;
				hasCurrentEma = true;
			})
			.Start();

		// Subscribe to trading timeframe for signals
		var tradeSub = SubscribeCandles(CandleType);
		tradeSub
			.Bind((ICandleMessage candle) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				if (!hasPreviousEma)
					return;

				var emaSlope = currentEma - previousEma;
				var close = candle.ClosePrice;
				var open = candle.OpenPrice;

				// Long: EMA slope positive, bearish candle (close < open), close near low
				if (emaSlope > 0 && close <= open && Position <= 0)
					BuyMarket();
				// Short: EMA slope negative, bullish candle (close > open), close near high
				else if (emaSlope < 0 && close >= open && Position >= 0)
					SellMarket();
			})
			.Start();

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