Открыть на GitHub

SAR Автоматическая стратегия

Пример демонстрирует простую торговую методику на основе индикатора Parabolic SAR. Стратегия открывает длинную позицию, когда цена закрытия выше значения SAR и позиция отсутствует, и открывает короткую позицию, когда цена ниже SAR. Дополнительно реализованы фиксированные стоп-лосс, тейк-профит и опциональный трейлинг-стоп.

Параметры

  • SarStep – коэффициент ускорения SAR.
  • SarMax – максимальный коэффициент ускорения SAR.
  • StopLoss – расстояние стоп-лосса в ценовых единицах.
  • TakeProfit – расстояние тейк-профита в ценовых единицах.
  • TrailingStop – расстояние трейлинг-стопа в ценовых единицах.
  • CandleType – тип свечей для расчётов.

Логика торговли

  1. Подписка на свечи и вычисление значений Parabolic SAR.
  2. Вход:
    • Покупка при отсутствии позиции, если SAR ниже цены закрытия.
    • Продажа при отсутствии позиции, если SAR выше цены закрытия.
  3. Выход:
    • Закрытие позиции при достижении противоположного уровня SAR.
    • Применение стоп-лосса, тейк-профита и трейлинг-стопа.

Стратегия предназначена для учебных целей и демонстрирует использование индикаторов и управления рисками в высокоуровневом API StockSharp.

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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simple Parabolic SAR based strategy with optional stop-loss,
/// take-profit and trailing stop management.
/// </summary>
public class SarAutomatedStrategy : Strategy
{
	private readonly StrategyParam<decimal> _sarStep;
	private readonly StrategyParam<decimal> _sarMax;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _entryPrice;
	private decimal _highestPrice;
	private decimal _lowestPrice;

	/// <summary>
	/// Parabolic SAR acceleration step.
	/// </summary>
	public decimal SarStep
	{
		get => _sarStep.Value;
		set => _sarStep.Value = value;
	}

	/// <summary>
	/// Parabolic SAR maximum acceleration.
	/// </summary>
	public decimal SarMax
	{
		get => _sarMax.Value;
		set => _sarMax.Value = value;
	}

	/// <summary>
	/// Stop loss in price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit in price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Trailing stop in price units.
	/// </summary>
	public decimal TrailingStop
	{
		get => _trailingStop.Value;
		set => _trailingStop.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="SarAutomatedStrategy"/>.
	/// </summary>
	public SarAutomatedStrategy()
	{
		_sarStep = Param(nameof(SarStep), 0.002m)
			.SetDisplay("SAR Step", "Acceleration factor for SAR", "Indicators");

		_sarMax = Param(nameof(SarMax), 0.02m)
			.SetDisplay("SAR Max", "Maximum acceleration for SAR", "Indicators");

		_stopLoss = Param(nameof(StopLoss), 3500m)
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk Management");

		_takeProfit = Param(nameof(TakeProfit), 6500m)
			.SetDisplay("Take Profit", "Take profit in price units", "Risk Management");

		_trailingStop = Param(nameof(TrailingStop), 800m)
			.SetDisplay("Trailing Stop", "Trailing stop in price units", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).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();

		_entryPrice = 0m;
		_highestPrice = 0m;
		_lowestPrice = 0m;
	}

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

		var sar = new ParabolicSar
		{
			Acceleration = SarStep,
			AccelerationMax = SarMax
		};

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position == 0)
		{
			if (sarValue < candle.ClosePrice)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_highestPrice = candle.ClosePrice;
				_lowestPrice = candle.ClosePrice;
			}
			else if (sarValue > candle.ClosePrice)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_highestPrice = candle.ClosePrice;
				_lowestPrice = candle.ClosePrice;
			}
		}
		else if (Position > 0)
		{
			_highestPrice = Math.Max(_highestPrice, candle.HighPrice);

			if (candle.HighPrice - _entryPrice >= TakeProfit)
			{
				SellMarket();
				return;
			}

			if (_entryPrice - candle.LowPrice >= StopLoss)
			{
				SellMarket();
				return;
			}

			if (TrailingStop > 0m && _highestPrice - candle.ClosePrice >= TrailingStop)
			{
				SellMarket();
				return;
			}

			if (sarValue > candle.ClosePrice)
			{
				SellMarket();
			}
		}
		else if (Position < 0)
		{
			_lowestPrice = Math.Min(_lowestPrice, candle.LowPrice);

			if (_entryPrice - candle.LowPrice >= TakeProfit)
			{
				BuyMarket();
				return;
			}

			if (candle.HighPrice - _entryPrice >= StopLoss)
			{
				BuyMarket();
				return;
			}

			if (TrailingStop > 0m && candle.ClosePrice - _lowestPrice >= TrailingStop)
			{
				BuyMarket();
				return;
			}

			if (sarValue < candle.ClosePrice)
			{
				BuyMarket();
			}
		}
	}
}