在 GitHub 上查看

Innocent Heikin Ashi Ethereum 策略

当价格在 EMA50 下方出现一系列看跌蜡烛后,若出现一根看涨蜡烛并站上 EMA50,该策略将在以太坊上开多单。止损设在最近 28 根K线的最低点,止盈根据 RiskReward 倍数计算。可选的 Moon Mode 允许在价格位于 EMA200 之上时进场。仓位也可能因卖出或陷阱信号而提前平仓。

细节

  • 入场条件
    • 做多:至少 ConfirmationLevel 根红色蜡烛在 EMA50 下方,随后一根绿色蜡烛站上 EMA50。
    • 激进:若启用 EnableMoonMode 且价格高于 EMA200。
  • 多空方向:仅多头。
  • 出场条件
    • 止损设在最近 28 根K线的最低点。
    • 止盈根据 RiskReward 倍数计算。
    • 可选的卖出或陷阱信号可提前平仓。
  • 止损:有。
  • 默认值
    • RiskReward = 1。
    • ConfirmationLevel = 1。
    • EnableMoonMode = true。
  • 过滤器
    • 类别:Trend following
    • 方向:Long
    • 指标:EMA
    • 止损:有
    • 复杂度:中等
    • 时间框架:日内
    • 季节性:无
    • 神经网络:无
    • 背离:无
    • 风险等级:中等
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>
/// Goes long on Ethereum after bearish activity below EMA50 followed by a
/// bullish candle above EMA50.
/// </summary>
public class InnocentHeikinAshiEthereumStrategy : Strategy {
    private readonly StrategyParam<decimal> _riskReward;
    private readonly StrategyParam<int> _confirmationLevel;
    private readonly StrategyParam<bool> _enableMoonMode;
    private readonly StrategyParam<bool> _showSellSignals;
    private readonly StrategyParam<bool> _showBullTraps;
    private readonly StrategyParam<bool> _showBearTraps;
    private readonly StrategyParam<DataType> _candleType;

    private ExponentialMovingAverage _ema50;
    private ExponentialMovingAverage _ema200;
    private Lowest _lowest;

    private int? _lastRedVectorBelowEma50;
    private int? _lastBuySignalIndex;
    private int? _lastSellSignalIndex;
    private int _redCountUnderEma50;
    private int _greenCountAboveEma200;
    private int _barIndex;
    private decimal _prevHaOpen;
    private decimal _prevHaClose;
    private decimal _stopPrice;
    private decimal _takePrice;

    /// <summary>
    /// Initializes a new instance of the <see
    /// cref="InnocentHeikinAshiEthereumStrategy"/> class.
    /// </summary>
    public InnocentHeikinAshiEthereumStrategy() {
	_riskReward =
	    Param(nameof(RiskReward), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Risk/Reward", "Take profit to stop ratio", "Risk")
		
		.SetOptimize(0.5m, 3m, 0.5m);

	_confirmationLevel =
	    Param(nameof(ConfirmationLevel), 1)
		.SetNotNegative()
		.SetDisplay(
		    "Confirmation Level",
		    "Number of red candles below EMA50 required before entry",
		    "General");

	_enableMoonMode =
	    Param(nameof(EnableMoonMode), true)
		.SetDisplay("Enable Moon Mode", "Allow entries above EMA200",
			    "General");

	_showSellSignals = Param(nameof(ShowSellSignals), true)
			       .SetDisplay("Show Sell Signals",
					   "Close on sell signals", "General");

	_showBullTraps =
	    Param(nameof(ShowBullTraps), true)
		.SetDisplay("Show Bull Traps",
			    "Close if next candle after buy is red", "General");

	_showBearTraps =
	    Param(nameof(ShowBearTraps), true)
		.SetDisplay("Show Bear Traps", "Close if sell signal fails",
			    "General");

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

    /// <summary>
    /// Risk/reward multiplier.
    /// </summary>
    public decimal RiskReward {
	get => _riskReward.Value;
	set => _riskReward.Value = value;
    }

    /// <summary>
    /// Required number of red candles below EMA50.
    /// </summary>
    public int ConfirmationLevel {
	get => _confirmationLevel.Value;
	set => _confirmationLevel.Value = value;
    }

    /// <summary>
    /// Allow aggressive entries above EMA200.
    /// </summary>
    public bool EnableMoonMode {
	get => _enableMoonMode.Value;
	set => _enableMoonMode.Value = value;
    }

    /// <summary>
    /// Close position on sell signals.
    /// </summary>
    public bool ShowSellSignals {
	get => _showSellSignals.Value;
	set => _showSellSignals.Value = value;
    }

    /// <summary>
    /// Close if the candle after a buy is bearish.
    /// </summary>
    public bool ShowBullTraps {
	get => _showBullTraps.Value;
	set => _showBullTraps.Value = value;
    }

    /// <summary>
    /// Close if sell signal fails.
    /// </summary>
    public bool ShowBearTraps {
	get => _showBearTraps.Value;
	set => _showBearTraps.Value = value;
    }

    /// <summary>
    /// Candle type used for calculations.
    /// </summary>
    public DataType CandleType {
	get => _candleType.Value;
	set => _candleType.Value = value;
    }

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

    /// <inheritdoc />
    protected override void OnReseted() {
	base.OnReseted();
	_lastRedVectorBelowEma50 = null;
	_lastBuySignalIndex = null;
	_lastSellSignalIndex = null;
	_redCountUnderEma50 = 0;
	_greenCountAboveEma200 = 0;
	_barIndex = 0;
	_prevHaOpen = 0m;
	_prevHaClose = 0m;
	_stopPrice = 0m;
	_takePrice = 0m;
    }

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

	_ema50 = new ExponentialMovingAverage { Length = 50 };
	_ema200 = new ExponentialMovingAverage { Length = 200 };
	_lowest = new Lowest { Length = 28 };

	var subscription = SubscribeCandles(CandleType);
	subscription.Bind(_ema50, _ema200, _lowest, ProcessCandle).Start();
    }

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

	decimal haOpen;
	decimal haClose;

	if (_barIndex == 0) {
	    haOpen = (candle.OpenPrice + candle.ClosePrice) / 2;
	    haClose = (candle.OpenPrice + candle.HighPrice + candle.LowPrice +
		       candle.ClosePrice) /
		      4;
	} else {
	    haOpen = (_prevHaOpen + _prevHaClose) / 2;
	    haClose = (candle.OpenPrice + candle.HighPrice + candle.LowPrice +
		       candle.ClosePrice) /
		      4;
	}

	var isGreen = haClose > haOpen;
	var isRed = !isGreen;

	if (isRed && candle.ClosePrice < ema50)
	    _redCountUnderEma50++;

	if (isRed && candle.OpenPrice < ema50 && candle.ClosePrice < ema50)
	    _lastRedVectorBelowEma50 = _barIndex;

	if (isGreen && candle.OpenPrice > ema200 && candle.OpenPrice > ema50)
	    _lastSellSignalIndex = _barIndex;

	if (isGreen && candle.ClosePrice > ema200)
	    _greenCountAboveEma200++;

	if (_lastRedVectorBelowEma50.HasValue && isGreen) {
	    _stopPrice = lowest;
	    _takePrice = candle.ClosePrice +
			 (candle.ClosePrice - _stopPrice) * RiskReward;
	}

	var canBuy =
	    _lastRedVectorBelowEma50.HasValue && isGreen &&
	    candle.OpenPrice > ema50 &&
	    (_lastBuySignalIndex == null || _barIndex > _lastBuySignalIndex);

	if (canBuy) {
	    if (candle.ClosePrice < ema200 &&
		_redCountUnderEma50 >= ConfirmationLevel) {
		BuyMarket();
		_lastBuySignalIndex = _barIndex;
		_lastRedVectorBelowEma50 = null;
		_redCountUnderEma50 = 0;
	    } else if (EnableMoonMode && candle.ClosePrice > ema200 &&
		       _redCountUnderEma50 >= ConfirmationLevel) {
		BuyMarket();
		_lastBuySignalIndex = _barIndex;
		_lastRedVectorBelowEma50 = null;
		_redCountUnderEma50 = 0;
	    }
	}

	if (Position > 0) {
	    if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice)
		SellMarket();
	}

	if (ShowSellSignals && _lastSellSignalIndex.HasValue && isRed &&
	    candle.OpenPrice > ema200 && candle.ClosePrice > ema200 &&
	    _barIndex == _lastSellSignalIndex + 1) {
	    if (_greenCountAboveEma200 >= ConfirmationLevel) {
		SellMarket();
		_lastSellSignalIndex = null;
		_greenCountAboveEma200 = 0;
	    }
	}

	if (ShowBullTraps && _lastBuySignalIndex.HasValue &&
	    _barIndex == _lastBuySignalIndex + 1 && isRed)
	    SellMarket();

	if (ShowBearTraps && _lastSellSignalIndex.HasValue &&
	    _barIndex == _lastSellSignalIndex + 1 && isGreen) {
	    SellMarket();
	    _lastSellSignalIndex = null;
	    _greenCountAboveEma200 = 0;
	}

	_prevHaOpen = haOpen;
	_prevHaClose = haClose;
	_barIndex++;
    }
}