Auf GitHub ansehen

Bot für Spot-Markt - Benutzerdefiniertes Grid-Strategie

Die Bot-für-Spot-Markt-Strategie mit benutzerdefiniertem Grid kauft eine Anfangsposition und fügt neue Aufträge hinzu, wenn der Preis um einen bestimmten Prozentsatz unter den letzten Einstieg fällt. Alle Positionen werden geschlossen, wenn der Preis das Gewinnziel über dem durchschnittlichen Einstiegspreis überschreitet.

Details

  • Einstiegskriterien:
    • Kauf zum Startzeitpunkt.
    • Zusätzliche Menge kaufen, wenn der Preis NextEntryPercent% unter den letzten Einstieg fällt.
  • Long/Short: Nur Long.
  • Ausstiegskriterien:
    • Alle Positionen schließen, wenn der Preis den durchschnittlichen Einstiegspreis um ProfitPercent% übersteigt und die offene Position profitabel ist.
  • Stops: Keine.
  • Standardwerte:
    • OrderValue = 10
    • MinAmountMovement = 0.00001
    • Rounding = 5
    • NextEntryPercent = 0.5
    • ProfitPercent = 2
  • Filter:
    • Kategorie: Grid-Trading
    • Richtung: Long
    • Indikatoren: Keine
    • Stops: Nein
    • Komplexität: Niedrig
    • Zeitrahmen: Beliebig
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • Risikolevel: Mittel
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>
/// Bot for Spot Market - Custom Grid strategy.
/// </summary>
public class BotForSpotMarketCustomGridStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderValue;
	private readonly StrategyParam<decimal> _minAmountMovement;
	private readonly StrategyParam<int> _rounding;
	private readonly StrategyParam<decimal> _nextEntryPercent;
	private readonly StrategyParam<decimal> _profitPercent;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<DateTimeOffset> _startTime;

	private decimal _lastEntryPrice;
	private decimal _avgPrice;
	private decimal _positionVolume;
	private bool _initialOrderSent;

	/// <summary>
	/// Order value in asset currency.
	/// </summary>
	public decimal OrderValue { get => _orderValue.Value; set => _orderValue.Value = value; }

	/// <summary>
	/// Minimum amount movement.
	/// </summary>
	public decimal MinAmountMovement { get => _minAmountMovement.Value; set => _minAmountMovement.Value = value; }

	/// <summary>
	/// Number of decimal places for rounding.
	/// </summary>
	public int Rounding { get => _rounding.Value; set => _rounding.Value = value; }

	/// <summary>
	/// Percentage drop from last entry to place next buy.
	/// </summary>
	public decimal NextEntryPercent { get => _nextEntryPercent.Value; set => _nextEntryPercent.Value = value; }

	/// <summary>
	/// Profit target percentage from average entry price.
	/// </summary>
	public decimal ProfitPercent { get => _profitPercent.Value; set => _profitPercent.Value = value; }

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

	/// <summary>
	/// Start time for trading.
	/// </summary>
	public DateTimeOffset StartTime { get => _startTime.Value; set => _startTime.Value = value; }

	/// <summary>
	/// Initializes a new instance of the <see cref="BotForSpotMarketCustomGridStrategy"/>.
	/// </summary>
	public BotForSpotMarketCustomGridStrategy()
	{
		_orderValue = Param(nameof(OrderValue), 10m)
			.SetDisplay("Order Value", "Desired order value", "Parameters");

		_minAmountMovement = Param(nameof(MinAmountMovement), 0.00001m)
			.SetDisplay("Min Amount Movement", "Minimum allowed amount movement", "Parameters");

		_rounding = Param(nameof(Rounding), 5)
			.SetDisplay("Rounding", "Decimal places for rounding", "Parameters");

		_nextEntryPercent = Param(nameof(NextEntryPercent), 10m)
			.SetDisplay("Next Entry Less Than (%)", "Price drop from last entry to add new order", "Parameters");

		_profitPercent = Param(nameof(ProfitPercent), 15m)
			.SetDisplay("Profit (%)", "Profit target from average price", "Parameters");

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

		_startTime = Param(nameof(StartTime), new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero))
			.SetDisplay("Start Time", "Time to begin trading", "Time");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_lastEntryPrice = 0;
		_avgPrice = 0;
		_positionVolume = 0;
		_initialOrderSent = false;
	}

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

		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 (candle.OpenTime < StartTime)
			return;

		var price = candle.ClosePrice;

		if (Position <= 0 && !_initialOrderSent)
		{
			BuyMarket();
			_lastEntryPrice = price;
			_avgPrice = price;
			_initialOrderSent = true;
			return;
		}

		if (Position > 0 && _lastEntryPrice > 0 && price < _lastEntryPrice * (1 - NextEntryPercent / 100m))
		{
			BuyMarket();
			return;
		}

		if (Position > 0 && _avgPrice > 0)
		{
			var target = _avgPrice * (1 + ProfitPercent / 100m);

			if (price > target)
				SellMarket();
		}
	}

	private decimal GetQuantity(decimal closePrice)
	{
		var price = OrderValue / closePrice;
		var round = Math.Round(price, Rounding);
		var quant = round >= price ? round + MinAmountMovement : round + (MinAmountMovement * 2m);
		return quant;
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		if (trade.Order == null)
			return;

		if (trade.Order.Side == Sides.Buy)
		{
			var newVolume = _positionVolume + trade.Trade.Volume;
			_avgPrice = (_avgPrice * _positionVolume + trade.Trade.Price * trade.Trade.Volume) / newVolume;
			_positionVolume = newVolume;
			_lastEntryPrice = trade.Trade.Price;
		}
		else if (trade.Order.Side == Sides.Sell)
		{
			_positionVolume -= trade.Trade.Volume;
			if (_positionVolume <= 0)
			{
				_positionVolume = 0;
				_avgPrice = 0;
				_lastEntryPrice = 0;
				_initialOrderSent = false;
			}
		}
	}
}