在 GitHub 上查看

S7 Up Bot 策略

基于突破的系统,寻找相近的高点或低点后出现的快速价格移动。 当连续两个低点几乎相等且价格上升 Span Price 时开多单。 当连续两个高点几乎相等且价格下降 Span Price 时做空。 仓位可通过止盈、止损、跟踪止损及提前退出进行保护。

细节

  • 入场条件:
    • 做多: 当前低点与前一个低点差值小于 HL Divergence,且价格高于低点 Span Price
    • 做空: 当前高点与前一个高点差值小于 HL Divergence,且价格低于高点 Span Price
  • 多空方向: 双向。
  • 出场条件:
    • 止盈或止损。
    • 跟踪止损或移动到保本位。
    • 当价格突破前高/前低(Exit At Extremum)或靠近反向水平(Exit At Reversal)时提前退出。
  • 止损机制: 绝对止盈和止损,可选跟踪。
  • 过滤器: 无。

参数

  • Take Profit – 以价格单位表示的止盈。
  • Stop Loss – 以价格单位表示的止损,0 表示自动计算。
  • HL Divergence – 连续高/低点允许的最大差值。
  • Span Price – 从极值到价格的距离要求。
  • Max Trades – 同时允许的最大交易数。
  • Use Trailing Stop – 启用跟踪止损。
  • Trail Stop – 跟踪止损距离。
  • Zero Trailing – 当出现盈利时移动止损。
  • Step Trailing – 调整保本止损的最小步长。
  • Exit At Extremum – 当价格突破前高或前低时退出。
  • Exit At Reversal – 当价格接近反向水平时退出。
  • Span To Revers – 触发反向退出的距离。
  • Candle Type – 分析所用的时间框架。
  • Order Volume – 每笔交易的数量。
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>
/// S7 Up Bot breakout strategy.
/// Opens long after double bottom and short after double top.
/// </summary>
public class S7UpBotStrategy : Strategy
{
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _hlDivergence;
	private readonly StrategyParam<decimal> _spanPrice;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevLow;
	private decimal _prevHigh;
	private decimal _entryPrice;
	private decimal _stopPrice;
	private decimal _takeProfitPrice;
	private bool _isLong;
	private bool _inPosition;

	public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
	public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
	public decimal HlDivergence { get => _hlDivergence.Value; set => _hlDivergence.Value = value; }
	public decimal SpanPrice { get => _spanPrice.Value; set => _spanPrice.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public S7UpBotStrategy()
	{
		_takeProfit = Param(nameof(TakeProfit), 500m)
			.SetDisplay("Take Profit", "Absolute take profit", "Risk");

		_stopLoss = Param(nameof(StopLoss), 300m)
			.SetDisplay("Stop Loss", "Absolute stop loss", "Risk");

		_hlDivergence = Param(nameof(HlDivergence), 100m)
			.SetGreaterThanZero()
			.SetDisplay("HL Divergence", "Max difference between highs or lows", "General");

		_spanPrice = Param(nameof(SpanPrice), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Span Price", "Distance from extreme to price", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for analysis", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevLow = 0m;
		_prevHigh = 0m;
		_entryPrice = 0m;
		_stopPrice = 0m;
		_takeProfitPrice = 0m;
		_isLong = false;
		_inPosition = false;
	}

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

		_prevLow = 0m;
		_prevHigh = 0m;
		_inPosition = false;

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

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

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

		var price = candle.ClosePrice;

		if (_inPosition)
			ManagePosition(candle, price);

		if (!_inPosition && _prevLow != 0m && _prevHigh != 0m)
			CheckEntry(candle, price);

		_prevLow = candle.LowPrice;
		_prevHigh = candle.HighPrice;
	}

	private void CheckEntry(ICandleMessage candle, decimal price)
	{
		// Double bottom: consecutive similar lows + bounce
		if (Math.Abs(candle.LowPrice - _prevLow) < HlDivergence &&
			price - candle.LowPrice > SpanPrice)
		{
			if (Position <= 0)
				BuyMarket();
			_inPosition = true;
			_isLong = true;
			_entryPrice = price;
			_stopPrice = price - StopLoss;
			_takeProfitPrice = price + TakeProfit;
		}
		// Double top: consecutive similar highs + drop
		else if (Math.Abs(candle.HighPrice - _prevHigh) < HlDivergence &&
			candle.HighPrice - price > SpanPrice)
		{
			if (Position >= 0)
				SellMarket();
			_inPosition = true;
			_isLong = false;
			_entryPrice = price;
			_stopPrice = price + StopLoss;
			_takeProfitPrice = price - TakeProfit;
		}
	}

	private void ManagePosition(ICandleMessage candle, decimal price)
	{
		if (_isLong)
		{
			if (candle.HighPrice >= _takeProfitPrice || candle.LowPrice <= _stopPrice)
			{
				SellMarket();
				_inPosition = false;
			}
		}
		else
		{
			if (candle.LowPrice <= _takeProfitPrice || candle.HighPrice >= _stopPrice)
			{
				BuyMarket();
				_inPosition = false;
			}
		}
	}
}