View on GitHub

Daily Target Strategy

Overview

DailyTargetStrategy replicates the MetaTrader 4 expert advisor "Daily Target". The strategy keeps trading open positions until the combined profit and loss for the current calendar day reaches a configured profit target or breaches a maximum loss limit. As soon as either threshold is hit, all active orders are cancelled and the position is flattened so trading remains paused until the next day begins.

Trading Logic

  1. Start-up
    • The strategy calls ResetDailySnapshot during OnStarted to store the current date and the realized PnL baseline.
    • SubscribeLevel1() delivers bid/ask updates which are required to evaluate floating profit accurately.
    • SubscribeTrades() captures the last executed price, providing a fallback when quotes are missing.
    • A one-minute Timer tick ensures that date changes are detected even when no market data arrives.
  2. PnL evaluation
    • EvaluateDailyThresholds recomputes the realized PnL (current PnL minus the stored baseline) and adds the floating PnL calculated from the latest bid/ask or last trade price.
    • If the total daily PnL crosses the configured target or drops below the negative loss limit, the strategy calls TriggerDailyStop.
  3. Emergency exit
    • TriggerDailyStop writes an informational log entry, cancels all pending orders, and sends the appropriate market order to flatten the remaining long or short exposure.
    • _dailyStopTriggered prevents re-entry during the same day. When the calendar date changes, ResetDailySnapshot clears this flag and records a new PnL baseline.

Parameters

Name Default Description
DailyTarget 10 Profit target in portfolio currency. Trading stops for the rest of the day once the total daily PnL meets or exceeds this value.
DailyMaxLoss 0 Maximum tolerated loss in portfolio currency. Set to zero to disable the loss filter. Trading is halted for the day once the total daily PnL drops below the negative threshold.

Notes

  • The strategy only manages the primary Security assigned to the strategy instance, mirroring the single-symbol behaviour of the MQL expert.
  • Floating PnL uses the best bid for long positions and the best ask for short positions. If no quote is available, the last trade price acts as a fallback to avoid stalling the evaluation.
  • No Python port is provided; only the C# high-level implementation is included in this package.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Daily Target strategy: TEMA crossover.
/// Buys when fast TEMA crosses above slow TEMA, sells on cross below.
/// </summary>
public class DailyTargetStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

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

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

	public DailyTargetStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast TEMA", "Fast TEMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow TEMA", "Slow TEMA period", "Indicators");
	}

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

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
	}

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

		if (_hasPrev)
		{
			if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
				BuyMarket();
			else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
				SellMarket();
		}
		else
		{
			if (fastValue > slowValue && Position <= 0)
				BuyMarket();
			else if (fastValue < slowValue && Position >= 0)
				SellMarket();
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
		_hasPrev = true;
	}
}