在 GitHub 上查看

Aeron JJN Scalper EA 策略

概述

本策略是 Aeron JJN Scalper 专家的 StockSharp 高级 API 版本。策略仅处理已结束的K线,寻找特定的两根K线反转结构,并把上一根强劲反向K线的开盘价作为模拟挂单触发价。一旦行情触及该价位,策略以市价建仓,同时根据 ATR 设置止损、止盈,并使用基于点差的追踪止损进行仓位管理。

核心思想:

  • 方向由两根K线组成的看涨/看跌反转形态决定。
  • 入场价来自最近一根满足条件的反向K线开盘价。
  • 信号K线的 ATR(8) 数值同时决定止损和止盈距离。
  • 追踪止损根据配置的点差阈值逐步向盈利方向移动保护价位。
  • 挂单价位在超过设定分钟数后自动失效。

交易规则

信号识别

  1. 仅在配置的时间框架(默认 1 分钟)内处理已收盘的K线。
  2. 根据交易品种的最小价格步长计算点值,对于 3 位或 5 位报价额外乘以 10,以模拟 MetaTrader 的点差定义。
  3. 保存最近 120 根K线,用于寻找参考K线。
  4. 做多信号 条件:
    • 当前K线收盘价高于开盘价;
    • 前一根K线为阴线,实体大于 DojiDiff1Pips
    • 向后查找最近一根实体大于 DojiDiff2Pips 的阴线,其开盘价作为买入触发价。
  5. 做空信号 条件:
    • 当前K线收盘价低于开盘价;
    • 前一根K线为阳线,实体大于 DojiDiff1Pips
    • 向后查找最近一根实体大于 DojiDiff2Pips 的阳线,其开盘价作为卖出触发价。
  6. 当同方向已存在待触发价位或 ATR 尚未形成时忽略新的信号。

挂单价位管理

  • 触发价位被视为挂单,如果价格始终未触及并超过 ResetMinutes 指定的有效期,将被自动取消。
  • 后续K线出现最高价 ≥ 买入价位(或最低价 ≤ 卖出价位)时,策略会发送市价单,在需要时平掉反向持仓并加上 Volume 的净头寸。
  • 建仓成功后会清除另一方向仍然存在的挂单价位。

止损、止盈与追踪

  • 建仓时记录信号K线的 ATR(8):
    • 多单:止损 = entry - ATR,止盈 = entry + ATR
    • 空单:止损 = entry + ATR,止盈 = entry - ATR
  • 每根完成的K线均会:
    • 检查价格是否触及止损或止盈,触发后以市价平仓;
    • 当浮盈至少达到 TrailingStopPips + TrailingStepPips 点时启动追踪,将保护价位更新为距离最新收盘价 TrailingStopPips 的位置,并且只朝盈利方向移动。
  • 若仓位被手动关闭,内部状态会自动重置。

参数

参数 默认值 说明
Volume 0.1 入场净头寸。若需要反向,策略会在此基础上加上当前持仓绝对值。
TrailingStopPips 5 追踪止损的基础点差(会转换为价格单位)。
TrailingStepPips 5 每次移动追踪止损前,价格需额外前进的点差。
ResetMinutes 10 挂单价位的有效时间(分钟)。
DojiDiff1Pips 10 触发信号的前一根K线最小实体(点差)。
DojiDiff2Pips 4 用作入场参考的K线最小实体(点差)。
CandleType 1 分钟 用于计算的K线类型。

实现说明

  • 策略完全基于已收盘K线运行,并在内存中保存挂单价位;当触发价被突破时,通过市价单完成入场,从而在高层 API 中模拟原始 EA 的逻辑。
  • 使用 AverageTrueRange 计算 ATR(8),保证每笔交易的止损与止盈距离固定。
  • 点值转换沿用了 MetaTrader 对 3/5 位报价的处理;若品种缺少 PriceStep 则使用默认值 1。
  • 维护 120 根历史K线,对应原策略 CopyRates 的 100 根窗口并保留余量。
  • 本版本未提供 Python 实现。

使用方法

  1. 将策略附加到目标证券与组合上。
  2. 根据品种调整时间框架、点差参数以及 ATR 条件。
  3. 启动策略后,它会自动跟踪信号、在触发价被突破时以市价入场,并负责止盈、止损及追踪管理。
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>
/// Port of the Aeron JJN Scalper expert advisor.
/// Detects reversal candle patterns (bullish candle after strong bearish, and vice versa)
/// and enters at breakout of the prior candle's open level.
/// </summary>
public class AeronJjnScalperEaStrategy : Strategy
{
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _bodyMinAtr;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;

	private AverageTrueRange _atr;
	private decimal _prevOpen;
	private decimal _prevClose;
	private bool _hasPrev;
	private int _cooldown;

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public decimal BodyMinAtr
	{
		get => _bodyMinAtr.Value;
		set => _bodyMinAtr.Value = value;
	}

	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

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

	public AeronJjnScalperEaStrategy()
	{
		_atrLength = Param(nameof(AtrLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Length", "ATR indicator period", "Indicators");

		_bodyMinAtr = Param(nameof(BodyMinAtr), 1.5m)
			.SetDisplay("Body Min ATR", "Minimum candle body size as ATR multiple", "Indicators");

		_cooldownBars = Param(nameof(CooldownBars), 10)
			.SetGreaterThanZero()
			.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle Type", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_hasPrev = false;
		_cooldown = 0;
		_prevOpen = 0;
		_prevClose = 0;
	}

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

		_atr = new AverageTrueRange { Length = AtrLength };
		_hasPrev = false;
		_cooldown = 0;

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

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent)
		);

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

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

		if (atrVal <= 0)
		{
			SavePrev(candle);
			return;
		}

		if (_cooldown > 0)
			_cooldown--;

		if (_hasPrev && _cooldown == 0 && Position == 0)
		{
			var prevBody = Math.Abs(_prevClose - _prevOpen);
			var minBody = atrVal * BodyMinAtr;

			// Bullish candle after strong bearish candle => buy
			if (candle.ClosePrice > candle.OpenPrice && _prevClose < _prevOpen && prevBody >= minBody)
			{
				BuyMarket();
				_cooldown = CooldownBars;
			}
			// Bearish candle after strong bullish candle => sell
			else if (candle.ClosePrice < candle.OpenPrice && _prevClose > _prevOpen && prevBody >= minBody)
			{
				SellMarket();
				_cooldown = CooldownBars;
			}
		}

		SavePrev(candle);
	}

	private void SavePrev(ICandleMessage candle)
	{
		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
		_hasPrev = true;
	}
}