在 GitHub 上查看

Hawaiian Tsunami Surfer

该策略寻找突然的动量尖峰并反向交易。它使用 Momentum 指标计算单个柱体收盘价的百分比变化。当变化超过微小阈值时,视为一次“海啸”。策略在强烈上升后卖出,在强烈下跌后买入。通过 StartProtection 以价格步长设置止损和止盈。

细节

  • 入场条件
    • 当动量百分比 > TsunamiStrength 时卖出。
    • 当动量百分比 < -TsunamiStrength 时买入。
  • 多空方向:双向。
  • 离场条件:止损或止盈。
  • 止损:是,通过 StartProtection。
  • 默认值
    • MomentumPeriod = 1
    • TsunamiStrength = 0.24
    • TakeProfitPoints = 500
    • StopLossPoints = 700
    • CandleType = TimeSpan.FromMinutes(1)
  • 筛选
    • 类别:均值回归
    • 方向:双向
    • 指标:Momentum
    • 止损:是
    • 复杂度:基础
    • 时间框架:日内
    • 季节性:否
    • 神经网络:否
    • 背离:否
    • 风险等级:高
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Contrarian strategy based on strong momentum spikes.
/// Sells when momentum percentage rises above a small threshold.
/// Buys when momentum drops below the negative threshold.
/// Uses protective stop-loss and take-profit in price steps.
/// </summary>
public class HawaiianTsunamiSurferStrategy : Strategy
{
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<decimal> _tsunamiStrength;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<DataType> _candleType;

	/// <summary>
	/// Momentum calculation period.
	/// </summary>
	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	/// <summary>
	/// Momentum deviation from 0% to trigger trades (in percent).
	/// </summary>
	public decimal TsunamiStrength
	{
		get => _tsunamiStrength.Value;
		set => _tsunamiStrength.Value = value;
	}

	/// <summary>
	/// Take-profit in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop-loss in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="HawaiianTsunamiSurferStrategy"/>.
	/// </summary>
	public HawaiianTsunamiSurferStrategy()
	{
		_momentumPeriod = Param(nameof(MomentumPeriod), 1)
		.SetDisplay("Momentum Period", "Period of the momentum indicator", "Indicators")
		.SetGreaterThanZero();

		_tsunamiStrength = Param(nameof(TsunamiStrength), 0.24m)
		.SetDisplay("Threshold", "Momentum percentage deviation from 0%", "Parameters")
		.SetGreaterThanZero();

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500)
		.SetDisplay("Take Profit Points", "Take profit distance in price steps", "Risk Management")
		.SetGreaterThanZero();

		_stopLossPoints = Param(nameof(StopLossPoints), 700)
		.SetDisplay("Stop Loss Points", "Stop loss distance in price steps", "Risk Management")
		.SetGreaterThanZero();

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

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

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

		// Create momentum indicator
		var momentum = new Momentum { Length = MomentumPeriod };

		// Subscribe to candles and bind the indicator
		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(momentum, ProcessCandle)
		.Start();

		// Setup chart
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, momentum);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal momentumValue)
	{
		// Process only finished candles
		if (candle.State != CandleStates.Finished)
		return;

		if (!IsFormedAndOnlineAndAllowTrading())
		return;

		// Convert momentum difference to percentage change around zero
		var closePrice = candle.ClosePrice;
		var previousPrice = closePrice - momentumValue;
		if (previousPrice == 0m)
		return;

		var percentChange = (momentumValue / previousPrice) * 100m;

		if (percentChange > TsunamiStrength && Position >= 0)
		{
			SellMarket();
		}
		else if (percentChange < -TsunamiStrength && Position <= 0)
		{
			BuyMarket();
		}
	}
}