Ver no GitHub

Close At Profit Strategy

This strategy monitors the realized profit and loss of all trades executed by the strategy. When the accumulated profit exceeds a user defined threshold, it immediately closes any open position and optionally cancels active orders. The same behavior can be enabled for drawdown by setting a loss limit.

The strategy does not analyze indicators or price movement. Instead, it acts as a protective overlay that exits the market once a monetary target or stop level is reached. A simple candle subscription is used only to provide periodic checks of the current PnL value.

Parameters

  • UseProfitToClose – enable or disable closing by profit target. Default: true.
  • ProfitToClose – profit value in currency units that triggers a full exit. Default: 20.
  • UseLossToClose – enable or disable closing by loss limit. Default: false.
  • LossToClose – loss value in currency units that triggers a full exit when exceeded. Default: 100.
  • ClosePendingOrders – cancel all active orders when closing positions. Default: true.
  • CandleType – type of candles used to trigger periodic checks. Default: 1 minute timeframe.

Trading Logic

  1. Subscribe to candles of the selected timeframe.
  2. On each finished candle calculate current realized PnL.
  3. If profit is greater than or equal to ProfitToClose, close the entire position and optionally cancel pending orders.
  4. If loss monitoring is enabled and current PnL is less than or equal to -LossToClose, close the entire position and optionally cancel pending orders.

Additional Notes

  • The strategy closes only the position for the security it is attached to.
  • Pending orders are canceled using the built-in CancelActiveOrders method.
  • The logic can be combined with other entry strategies to implement profit taking or portfolio protection.

Filters

  • Category: Risk management
  • Direction: Both
  • Indicators: None
  • Stops: Yes
  • Complexity: Basic
  • Timeframe: Any
  • Seasonality: No
  • Neural networks: No
  • Divergence: No
  • Risk level: Medium
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>
/// EMA crossover strategy with profit/loss exit targets.
/// </summary>
public class CloseAtProfitStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public CloseAtProfitStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		SubscribeCandles(CandleType).Bind(fast, slow, ProcessCandle).Start();
	}

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

		if (!_hasPrev)
		{
			_prevFast = fast;
			_prevSlow = slow;
			_hasPrev = true;
			return;
		}

		// Fast crosses above slow => buy
		if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
		}
		// Fast crosses below slow => sell
		else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
	}
}