Ver no GitHub

BuySellStopButtons Strategy

Overview

  • Recreates the MetaTrader 4 "Buy Sell Stop Buttons" expert advisor inside StockSharp.
  • Provides three manual parameters (BuyRequest, SellRequest, CloseRequest) that emulate the chart buttons.
  • Implements the same money management helpers: fixed money take-profit, percent take-profit, equity trailing lock, break-even and pip trailing stops.
  • Uses a one minute candle subscription purely as a heartbeat to evaluate the management rules on finished bars.

Parameters

Name Description
OrderLots Base lot size used when a manual entry is requested. Mirrors the Lots extern input (0.01 by default).
NumberOfTrades Number of tickets dispatched per request. The C# port nets the volume into a single market order.
UseTakeProfitInMoney / TakeProfitInMoney Enable and configure the direct money target that closes all trades when reached.
UseTakeProfitPercent / TakeProfitPercent Enable and configure the equity percentage target. The strategy uses Portfolio.CurrentValue to approximate account balance.
EnableTrailing, TrailingProfitMoney, TrailingLossMoney Configure the equity trailing block: once profit exceeds TrailingProfitMoney, the peak is tracked and all trades close if profit retraces by TrailingLossMoney.
UseBreakEven, BreakEvenTriggerPips, BreakEvenOffsetPips Move the stop to break-even plus offset after the position earns the configured pip distance.
StopLossPips, TakeProfitPips, TrailingStopPips Ticket management settings converted to pip distances in StockSharp.
CandleType Candle series that drives the heartbeat (default one-minute candles).
BuyRequest, SellRequest, CloseRequest Manual commands that replace the original chart buttons. The flags reset automatically after the action succeeds.

Trading Logic

  1. OnStarted subscribes to the configured candle series, sets the base Volume and enables the built-in position protection.
  2. Each finished candle triggers the following workflow:
    • Manual commands are evaluated: buy and sell send a market order with OrderLots * NumberOfTrades volume, optionally offsetting an opposite position; close requests flatten the strategy.
    • Money targets are checked in order: fixed amount, percent of equity, then the trailing equity lock.
    • Break-even and pip trailing stops adjust internal stop levels based on the average entry price.
    • Static stop-loss/take-profit distances are enforced.
    • Optional Bollinger-band exit closes longs touching the upper band or shorts touching the lower band (20 period, width 2).
  3. Open profit is calculated with Security.PriceStep/Security.StepPrice when available; otherwise a price-difference fallback is used.

Differences from the MQL Version

  • MetaTrader allowed hedged positions; StockSharp nets exposure, so buy/sell requests first neutralize opposite positions.
  • Monthly MACD-based exits (Close_BUY/Close_SELL) are not present because they were never called in the original script.
  • Volume auto-scaling via MaximumRisk/DecreaseFactor is replaced by the explicit OrderLots parameter. The MQL helper relied on account history that is not available in this port.
  • Stop adjustments are driven by finished candles instead of raw ticks, matching repository guidelines.
  • Indicator values are processed through Bind, avoiding direct collections or manual history buffers.

Usage Notes

  • Keep BuyRequest, SellRequest and CloseRequest under the "Manual controls" group disabled when running optimizations.
  • The trailing equity lock and money take-profit logic require Security.StepPrice to translate profit into currency. When it is unavailable the fallback uses pure price differences.
  • Break-even and trailing stops use the instrument pip size inferred from MinPriceStep/PriceStep and decimal digits.
  • There is no Python translation, as requested.

Testing

  • No automated tests were modified; the strategy integrates with the existing solution structure and relies on manual parameter toggles for verification.
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;

public class BuySellStopButtonsStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public BuySellStopButtonsStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}