在 GitHub 上查看

AML 烛体交叉策略

该策略基于自适应市场水平 (AML) 指标。 当 AML 值落在当前K线实体内部时发起交易: 如果收盘价高于开盘价且 AML 位于其间,则开多单; 若收盘价低于开盘价且 AML 位于其间,则开空单。 当出现相反信号且允许时,策略会反向开仓。

细节

  • 入场条件
    • 多头:阳线且 open <= AML <= close
    • 空头:阴线且 open >= AML >= close
  • 方向:双向。
  • 出场条件:启用时在相反信号出现时反向开仓。
  • 止损:无。
  • 默认值
    • Fractal = 70
    • Lag = 18
    • Shift = 0
    • UseOpposite = true
  • 过滤器
    • 类别:趋势跟随
    • 方向:双向
    • 指标:单一 (AML)
    • 止损:无
    • 复杂度:中等
    • 时间框架:短期
    • 季节性:无
    • 神经网络:无
    • 背离:无
    • 风险等级:中等
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>
/// Adaptive Market Level candle cross strategy.
/// Opens position when AML value lies between candle open and close.
/// Reverses position if opposite condition occurs.
/// </summary>
public class AmlCandleCrossStrategy : Strategy
{
	private readonly StrategyParam<int> _fractal;
	private readonly StrategyParam<int> _lag;
	private readonly StrategyParam<DataType> _candleType;

	public int Fractal { get => _fractal.Value; set => _fractal.Value = value; }
	public int Lag { get => _lag.Value; set => _lag.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public AmlCandleCrossStrategy()
	{
		_fractal = Param(nameof(Fractal), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fractal", "Fractal window size", "General");
		_lag = Param(nameof(Lag), 5)
			.SetGreaterThanZero()
			.SetDisplay("Lag", "Lag for smoothing", "General");
		_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 OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var aml = new AdaptiveMarketLevel
		{
			Fractal = Fractal,
			Lag = Lag,
		};

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

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

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

		if (amlValue == 0)
			return;

		var open = candle.OpenPrice;
		var close = candle.ClosePrice;

		// Bullish: AML between open and close, bullish candle
		var bullish = close > open && amlValue >= open && amlValue <= close;
		// Bearish: AML between close and open, bearish candle
		var bearish = close < open && amlValue >= close && amlValue <= open;

		if (Position == 0)
		{
			if (bullish)
				BuyMarket();
			else if (bearish)
				SellMarket();
		}
	}
}

/// <summary>
/// Adaptive Market Level indicator.
/// </summary>
public class AdaptiveMarketLevel : BaseIndicator
{
	private int _pos;
	private decimal[] _smooth = Array.Empty<decimal>();
	private decimal _lastValue;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();

	public int Fractal { get; set; } = 10;
	public int Lag { get; set; } = 5;

	public override void Reset()
	{
		base.Reset();
		_pos = 0;
		_smooth = new decimal[Lag + 1];
		_lastValue = 0;
		_highs.Clear();
		_lows.Clear();
	}

	protected override IIndicatorValue OnProcess(IIndicatorValue input)
	{
		var candle = input.GetValue<ICandleMessage>();

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		if (_highs.Count < Fractal * 2 || _highs.Count <= Lag)
			return new DecimalIndicatorValue(this, 0m, input.Time);

		IsFormed = true;

		decimal r1 = Range(Fractal, 0) / Fractal;
		decimal r2 = Range(Fractal, Fractal) / Fractal;
		decimal r3 = Range(Fractal * 2, 0) / (Fractal * 2);

		double dim = 0;
		if (r1 + r2 > 0 && r3 > 0)
			dim = (Math.Log((double)(r1 + r2)) - Math.Log((double)r3)) * 1.44269504088896;

		var alpha = (decimal)Math.Exp(-Lag * (dim - 1.0));
		if (alpha > 1m) alpha = 1m;
		if (alpha < 0.01m) alpha = 0.01m;

		var price = (candle.HighPrice + candle.LowPrice + 2m * candle.OpenPrice + 2m * candle.ClosePrice) / 6m;

		var prevPos = (_pos - 1 + _smooth.Length) % _smooth.Length;
		_smooth[_pos] = alpha * price + (1m - alpha) * _smooth[prevPos];

		var lagPos = (_pos - Lag + _smooth.Length) % _smooth.Length;
		var step = 0.01m;
		var current = Math.Abs(_smooth[_pos] - _smooth[lagPos]) >= Lag * Lag * step ? _smooth[_pos] : _lastValue;

		_lastValue = current;
		_pos = (_pos + 1) % _smooth.Length;

		return new DecimalIndicatorValue(this, current, input.Time);
	}

	private decimal Range(int count, int offset)
	{
		var end = _highs.Count - 1 - offset;
		var start = end - count + 1;
		if (start < 0) start = 0;

		var max = decimal.MinValue;
		var min = decimal.MaxValue;

		for (var i = start; i <= end; i++)
		{
			if (_highs[i] > max) max = _highs[i];
			if (_lows[i] < min) min = _lows[i];
		}

		return max - min;
	}
}