View on GitHub

Triangle Strategy

This strategy ports the Triangle v1 MetaTrader expert advisor to the StockSharp high-level API. The original EA combined weighted moving average filters on a higher timeframe, a momentum divergence check, and a very long-term MACD confirmation before placing breakout-style orders. The StockSharp version keeps the multi-timeframe logic while replacing tick-by-tick money management with candle-based protective orders.

How it Works

  1. Multi-timeframe filters. The working timeframe (CandleType, default 15 minutes) is used for executing trades. Trend and momentum filters are calculated on a higher timeframe (TrendCandleType, default 1 hour) to mirror the MQL calls that referenced T.
  2. LWMA trend gate. Fast and slow weighted moving averages (LWMA equivalent) must be aligned. Long setups require the fast LWMA to stay above the slow LWMA; shorts demand the opposite relation.
  3. Momentum deviation. A 14-period momentum series on the higher timeframe must deviate from the neutral level (100) by at least MomentumThreshold on any of the last three completed candles, reproducing the MomLevelB/MomLevelS checks.
  4. MACD confirmation. A very high timeframe (MacdCandleType, default 30-day candles ≈ monthly) MACD must show the main line on the correct side of the signal line before trades are allowed, copying the MacdMAIN0 vs MacdSIGNAL0 condition.
  5. Protective exits. Stop loss and take profit distances are configured in price steps. When either level is reached on a completed bar the strategy closes the position with a market order.

Parameters

Parameter Description
FastMaPeriod, SlowMaPeriod Lengths of the higher-timeframe weighted moving averages.
MomentumPeriod Period for the momentum filter on the higher timeframe.
MomentumThreshold Minimum absolute deviation from 100 required on any of the last three momentum readings. Set to 0 to disable the filter.
MacdFastLength, MacdSlowLength, MacdSignalLength MACD parameters applied to MacdCandleType.
StopLossSteps, TakeProfitSteps Protective stop and target distances measured in instrument price steps (ticks). Use 0 to disable.
CandleType Trading timeframe used for order execution.
TrendCandleType Higher timeframe that feeds LWMAs and momentum.
MacdCandleType Timeframe used for the MACD confirmation filter.

Usage

  1. Select a security and configure CandleType, TrendCandleType, and MacdCandleType to match the timeframes you want to analyse.
  2. Adjust MA, momentum, and MACD lengths if you want to adapt the system to a different market or volatility regime.
  3. Set StopLossSteps and TakeProfitSteps according to the instrument tick size. The strategy converts the step counts to actual price distances automatically.
  4. Start the strategy. It subscribes to all required candle streams, updates indicators with the high-level Bind API, and manages the position when stops or targets are hit.

Differences from the Original EA

  • Money-based exits (Use_TP_In_Money, Use_TP_In_percent) and the balance-protection block are not recreated because StockSharp works in instrument units. Equivalent behaviour can be achieved by tuning StopLossSteps/TakeProfitSteps.
  • Trailing-stop, break-even, and equity-stop logic from the EA relied on tick processing and MetaTrader-specific order modification calls. The port keeps the simpler fixed-stop approach for clarity; users can extend UpdatePositionState with trailing rules if desired.
  • Manual trendlines (TREND/TRENDLOW) and fractal arrays were used as discretionary filters in the EA. They are intentionally omitted so the StockSharp strategy remains fully systematic.
  • The strategy always holds at most one net position, which matches typical usage even though the EA exposed a Max_Trades parameter.

Tune the thresholds and timeframe parameters to fit the instrument you trade. Wider values are usually required for volatile markets to avoid being filtered out by small momentum fluctuations.

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 TriangleStrategy : 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 TriangleStrategy()
	{
		_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;
	}
}