Auf GitHub ansehen

Straddle Trail v2.40 Strategy

The Straddle Trail v2.40 Strategy is a StockSharp port of the MetaTrader 4 expert advisor "Straddle&Trail" (version 2.40). The algorithm prepares a symmetrical pair of stop orders ahead of a high-impact event, automatically manages the triggered position with break-even and trailing-stop logic, and can react to manual trades that already exist on the account.

Core workflow

  1. Preparation
    • The strategy subscribes to order book updates to keep track of the best bid/ask and to minute candles (configurable) for scheduling decisions.
    • Pips are calculated from the instrument settings so that all distances defined in pips are properly converted to prices.
  2. Straddle placement
    • At the configured lead time before the event (PreEventEntryMinutes), or immediately if PlaceStraddleImmediately is enabled, a buy-stop and a sell-stop order are placed at DistanceFromPrice pips above and below the market.
    • Before the event, pending orders can be recentered every minute if AdjustPendingOrders is enabled. Adjustments stop StopAdjustMinutes before the event.
  3. Order management
    • Once one side is triggered, the optional removal of the opposite pending order (RemoveOppositeOrder) prevents double exposure.
    • ShutdownNow together with ShutdownOption makes it possible to flatten open positions and/or cancel pending orders on demand.
  4. Position protection
    • Initial stop-loss and take-profit levels are derived from the pip-based parameters.
    • When the price reaches the break-even trigger, the stop is moved to lock in BreakevenLockPips of profit.
    • Trailing starts either immediately or after break-even (depending on TrailAfterBreakeven).
    • If ManageManualTrades is true, any manual positions detected by the strategy will be protected using the same rules.

Parameters

Parameter Description
ShutdownNow Forces the shutdown logic to execute on the next candle close.
ShutdownOption Chooses what to close: everything, only triggered positions, long-only, short-only, all pending orders, only buy stops, or only sell stops.
DistanceFromPrice Distance in pips between the current price and the pending stop orders.
StopLossPips Initial stop-loss distance in pips.
TakeProfitPips Initial take-profit distance in pips. Set to 0 to disable the take-profit level.
TrailPips Trailing-stop distance in pips. Set to 0 to disable trailing.
TrailAfterBreakeven If true, trailing will only start after the break-even trigger is hit.
BreakevenLockPips Profit (in pips) locked in once the break-even trigger fires.
BreakevenTriggerPips Profit threshold (in pips) that activates the break-even move.
EventHour / EventMinute Scheduled news event time (broker time). Set both to 0 to disable the schedule and use the manual/immediate mode.
PreEventEntryMinutes Minutes before the event when the straddle is placed.
StopAdjustMinutes Minutes before the event when order adjustments stop. Minimum value is 1 minute.
RemoveOppositeOrder Removes the opposite pending order after one side of the straddle is filled.
AdjustPendingOrders Re-centers the pending orders every minute until the stop-adjust window is reached.
PlaceStraddleImmediately Places the straddle as soon as the strategy starts, ignoring the event schedule.
ManageManualTrades Extends the break-even and trailing logic to manual positions.
CandleType Candle series used for the timing and scheduling logic (default is 1-minute time frame).

Usage notes

  • Always configure the correct pip size for the instrument through the security settings so that pip-based distances translate to prices accurately.
  • The strategy closes positions using market orders when a stop-loss or take-profit condition is met, which mirrors how the original EA performed manual stop adjustments.
  • When PlaceStraddleImmediately is disabled and the schedule is active, the straddle is placed only once per trading day. Reset the strategy to prepare for another event on the same day.
  • The shutdown controls can be used as an emergency brake to quickly flatten exposure and remove pending orders across scenarios.

Conversion details

  • All comments in the code have been translated to English and expanded with additional explanations for clarity.
  • High-level StockSharp API methods (BuyStop, SellStop, ClosePosition) are used to keep the implementation close to the framework best practices.
  • The algorithm avoids direct indicator lookups and instead relies on the bound candle and order book subscriptions, as required by the project guidelines.
using System;
using System.Linq;
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>
/// Straddle breakout strategy converted from the MetaTrader 4 Straddle and Trail EA (v2.40).
/// Uses price channel breakouts with trailing stop management.
/// </summary>
public class StraddleTrailV240Strategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();

	/// <summary>
	/// Channel lookback period for breakout detection.
	/// </summary>
	public int ChannelPeriod
	{
		get => _channelPeriod.Value;
		set => _channelPeriod.Value = value;
	}

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

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

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

	/// <summary>
	/// Initializes a new instance.
	/// </summary>
	public StraddleTrailV240Strategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Channel Period", "Lookback period for breakout levels", "Parameters");

		_stopLoss = Param(nameof(StopLoss), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop loss distance", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take profit distance", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Candle subscription used", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highs.Clear();
		_lows.Clear();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		var ema = new EMA { Length = 10 };

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

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

		var tp = TakeProfit > 0 ? new Unit(TakeProfit, UnitTypes.Absolute) : null;
		var sl = StopLoss > 0 ? new Unit(StopLoss, UnitTypes.Absolute) : null;
		StartProtection(tp, sl);

		base.OnStarted2(time);
	}

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

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		while (_highs.Count > ChannelPeriod)
			_highs.RemoveAt(0);
		while (_lows.Count > ChannelPeriod)
			_lows.RemoveAt(0);

		if (_highs.Count < ChannelPeriod)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var upper = _highs.Take(_highs.Count - 1).Max();
		var lower = _lows.Take(_lows.Count - 1).Min();

		// Breakout above channel -> buy
		if (candle.ClosePrice > upper && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
		}
		// Breakout below channel -> sell
		else if (candle.ClosePrice < lower && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Position);
			SellMarket(Volume);
		}
	}

	/// <summary>
	/// Shutdown modes enum (preserved from original).
	/// </summary>
	public enum ShutdownModes
	{
		All = 0,
		TriggeredPositions = 1,
		TriggeredLong = 2,
		TriggeredShort = 3,
		PendingOrders = 4,
		PendingLong = 5,
		PendingShort = 6
	}
}