在 GitHub 上查看

HelloSmart策略

该策略实现单向网格交易。市场每向上次入场价反向移动设定的跳数,就会加仓一次。当累计仓位达到阈值时,下一笔订单的手数按乘数放大。当总盈亏达到设定的盈利或亏损限额时,平掉所有仓位。

参数

  • Trade Direction – 1 仅做多,2 仅做空。
  • Step – 加仓前价格需反向运动的跳数。
  • Initial Lot – 第一笔订单的基础手数。
  • Threshold Volume – 触发手数放大的累计仓位。
  • Maximum Lot – 单笔订单允许的最大手数。
  • Profit Target – 达到该盈利金额后全部平仓。
  • Loss Limit – 达到该亏损金额后全部平仓。
  • Lot Multiplier – 累计仓位达到阈值后应用到下一笔订单的乘数。
  • Candle Type – 用于计算价格运动的K线类型。
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>
/// Grid strategy that opens sequential orders in a single direction.
/// Closes all positions on reaching profit or loss limits.
/// </summary>
public class HelloSmartStrategy : Strategy
{
	public enum TradeModes
	{
		Buy,
		Sell
	}

	private readonly StrategyParam<TradeModes> _tradeMode;
	private readonly StrategyParam<decimal> _stepTicks;
	private readonly StrategyParam<decimal> _profitTarget;
	private readonly StrategyParam<decimal> _lossLimit;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _lastPrice;

	public TradeModes Mode { get => _tradeMode.Value; set => _tradeMode.Value = value; }
	public decimal StepTicks { get => _stepTicks.Value; set => _stepTicks.Value = value; }
	public decimal ProfitTarget { get => _profitTarget.Value; set => _profitTarget.Value = value; }
	public decimal LossLimit { get => _lossLimit.Value; set => _lossLimit.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public HelloSmartStrategy()
	{
		_tradeMode = Param(nameof(Mode), TradeModes.Sell)
			.SetDisplay("Trade Direction", "Buy or Sell direction", "General");
		_stepTicks = Param(nameof(StepTicks), 300m)
			.SetGreaterThanZero()
			.SetDisplay("Step", "Price movement to add position", "Risk");
		_profitTarget = Param(nameof(ProfitTarget), 60m)
			.SetDisplay("Profit Target", "Close all positions on this profit", "Risk");
		_lossLimit = Param(nameof(LossLimit), 5100m)
			.SetDisplay("Loss Limit", "Close all positions on this loss", "Risk");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_lastPrice = 0m;
	}

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

		_lastPrice = 0m;

		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;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var price = candle.ClosePrice;
		var stepPrice = StepTicks * 0.01m;

		// Check PnL limits first
		if (Position != 0 && (PnL > ProfitTarget || PnL < -LossLimit))
		{
			if (Position > 0)
				SellMarket();
			else
				BuyMarket();
			_lastPrice = price;
			return;
		}

		if (Mode == TradeModes.Buy)
		{
			var needOpen = Position <= 0 || (Position > 0 && (_lastPrice - price) >= stepPrice);
			if (needOpen)
			{
				BuyMarket();
				_lastPrice = price;
			}
		}
		else
		{
			var needOpen = Position >= 0 || (Position < 0 && (price - _lastPrice) >= stepPrice);
			if (needOpen)
			{
				SellMarket();
				_lastPrice = price;
			}
		}
	}
}