在 GitHub 上查看

Genie 策略

Genie 是一套基于 Parabolic SAR 的策略,并使用平均趋向指数(ADX)确认趋势强度。当 SAR 相对价格翻转且 ADX 的 +DI 和 -DI 互换主导时,策略开仓。风险通过跟踪止损和固定止盈进行管理。

测试表明,该方法在具有中等波动性的趋势性市场中效果最佳。

细节

  • 入场条件
    • 做多:前一根 SAR 在前一收盘价之上,当前 SAR 在当前收盘价之下,前一 +DI < 前一 -DI,当前 +DI > 当前 -DI,且 ADX 高于当前 +DI 和 -DI。
    • 做空:前一根 SAR 在前一收盘价之下,当前 SAR 在当前收盘价之上,前一 +DI > 前一 -DI,当前 +DI < 当前 -DI,且 ADX 高于当前 +DI 和 -DI。
  • 多/空:双向。
  • 离场条件
    • 触发跟踪止损或前一根 K 线与持仓方向相反。
  • 止损:有,跟踪止损和以价格单位计的止盈。
  • 默认值
    • TakeProfit = 500
    • TrailingStop = 200
    • SarStep = 0.02
    • AdxPeriod = 14
  • 过滤器
    • 类别:趋势跟随
    • 方向:双向
    • 指标:Parabolic SAR, ADX
    • 止损:有
    • 复杂度:中等
    • 周期:中期
    • 季节性:无
    • 神经网络:无
    • 背离:有(+DI 与 -DI)
    • 风险水平:中等
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>
/// Parabolic SAR strategy with momentum confirmation and trailing protection.
/// </summary>
public class GenieStrategy : Strategy
{
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<decimal> _sarStep;
	private readonly StrategyParam<int> _adxPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<decimal> _rsiLongLevel;
	private readonly StrategyParam<decimal> _rsiShortLevel;

	private decimal _prevSar;
	private ICandleMessage _prevCandle = null!;
	private int _cooldownRemaining;

	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	public decimal TrailingStop
	{
		get => _trailingStop.Value;
		set => _trailingStop.Value = value;
	}

	public decimal SarStep
	{
		get => _sarStep.Value;
		set => _sarStep.Value = value;
	}

	public int AdxPeriod
	{
		get => _adxPeriod.Value;
		set => _adxPeriod.Value = value;
	}

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

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

	public decimal RsiLongLevel
	{
		get => _rsiLongLevel.Value;
		set => _rsiLongLevel.Value = value;
	}

	public decimal RsiShortLevel
	{
		get => _rsiShortLevel.Value;
		set => _rsiShortLevel.Value = value;
	}

	public GenieStrategy()
	{
		_takeProfit = Param(nameof(TakeProfit), 500m)
			.SetDisplay("Take Profit", "Take profit distance", "Protection");

		_trailingStop = Param(nameof(TrailingStop), 200m)
			.SetDisplay("Trailing Stop", "Trailing stop distance", "Protection");

		_sarStep = Param(nameof(SarStep), 0.02m)
			.SetDisplay("SAR Step", "Acceleration factor", "Indicator");

		_adxPeriod = Param(nameof(AdxPeriod), 14)
			.SetDisplay("Momentum Period", "Period for momentum confirmation", "Indicator");

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

		_cooldownBars = Param(nameof(CooldownBars), 4)
			.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");

		_rsiLongLevel = Param(nameof(RsiLongLevel), 55m)
			.SetDisplay("RSI Long", "Minimum RSI level for long entries", "Filters");

		_rsiShortLevel = Param(nameof(RsiShortLevel), 45m)
			.SetDisplay("RSI Short", "Maximum RSI level for short entries", "Filters");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevSar = 0m;
		_prevCandle = null;
		_cooldownRemaining = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var sar = new ParabolicSar { AccelerationStep = SarStep, AccelerationMax = 0.2m };
		var rsi = new RelativeStrengthIndex { Length = AdxPeriod };
		var subscription = SubscribeCandles(CandleType);

		subscription.BindEx(sar, rsi, ProcessCandle).Start();

		StartProtection(
			takeProfit: new Unit(TakeProfit, UnitTypes.Absolute),
			stopLoss: new Unit(TrailingStop, UnitTypes.Absolute),
			isStopTrailing: true,
			useMarketOrders: true);

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

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue sarValue, IIndicatorValue rsiValue)
	{
		if (candle.State != CandleStates.Finished || !sarValue.IsFinal || !rsiValue.IsFinal)
			return;

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var sarCurrent = sarValue.ToDecimal();
		var rsi = rsiValue.ToDecimal();
		if (_prevCandle == null)
		{
			_prevSar = sarCurrent;
			_prevCandle = candle;
			return;
		}

		var sellCondition = _cooldownRemaining == 0 &&
			_prevSar < _prevCandle.ClosePrice &&
			sarCurrent > candle.ClosePrice &&
			rsi <= RsiShortLevel;

		var buyCondition = _cooldownRemaining == 0 &&
			_prevSar > _prevCandle.ClosePrice &&
			sarCurrent < candle.ClosePrice &&
			rsi >= RsiLongLevel;

		if (Position == 0)
		{
			if (sellCondition)
			{
				SellMarket();
				_cooldownRemaining = CooldownBars;
			}
			else if (buyCondition)
			{
				BuyMarket();
				_cooldownRemaining = CooldownBars;
			}
		}
		else if (Position > 0 && _prevCandle.OpenPrice > _prevCandle.ClosePrice)
		{
			SellMarket();
			_cooldownRemaining = CooldownBars;
		}
		else if (Position < 0 && _prevCandle.OpenPrice < _prevCandle.ClosePrice)
		{
			BuyMarket();
			_cooldownRemaining = CooldownBars;
		}

		_prevSar = sarCurrent;
		_prevCandle = candle;
	}
}