Multi Time Frame Trader Strategy
This strategy recreates the original MQL "Multi Time Frame Trader" logic with StockSharp high-level APIs. It combines three polynomial regression channels (M1, M5 and H1) and only trades when the lower time frames test their channel extremes in the direction suggested by the hourly slope.
The system continuously recomputes the regression channel upper, middle and lower bands on every finished candle. When the hourly upper band decreases, the bias is bearish; when it rises, the bias is bullish. Entries are triggered once the M5 and M1 candles reach the corresponding band and the directional filter agrees.
Core workflow
- Subscriptions: the strategy listens to 1-minute, 5-minute and 1-hour candles simultaneously.
- Regression channel: each subscription builds a polynomial regression line (degree 1-3) over
Barspoints and offsets it byStdMultiplierstandard deviations to obtain resistance and support bands. - Slope estimation: the channel slope is derived from the difference between the current upper band and the upper band
Barscandles ago, mirroring thei-Regrindicator behaviour. - Directional filter: the H1 slope defines whether only shorts (negative slope) or longs (positive slope) are allowed.
Entry logic
Short trades
- Hourly slope is negative.
- Latest 5-minute candle high touches or breaks the 5-minute regression resistance.
- Latest 1-minute candle high touches or breaks the 1-minute resistance.
- No existing short position is open (
Position >= 0). - A market sell order is sent, the stop loss is set half a channel width above the entry and the target equals the M5 midline.
Long trades
- Hourly slope is positive.
- Latest 5-minute candle low touches or breaks the 5-minute regression support.
- Latest 1-minute candle low touches or breaks the 1-minute support.
- No existing long position is open (
Position <= 0). - A market buy order is sent, the stop loss is placed half a channel width below the entry and the target equals the M5 midline.
Exit rules
- Stops and targets are stored internally and evaluated on every finished M1 candle. If the candle range crosses the stored stop level, the position is closed immediately.
- If the profit target is reached before the stop, the position is also closed.
- Closing resets the tracked levels so a fresh signal can be evaluated without delay.
Parameters
| Parameter | Default | Description |
|---|---|---|
Degree |
1 | Polynomial order of the regression channel (1=linear, 2=parabolic, 3=cubic). |
StdMultiplier |
2.0 | Multiplier for the standard deviation that defines band width. |
Bars |
250 | Number of candles used for regression fitting and slope lookback. |
Shift |
0 | Horizontal shift for the regression evaluation point (clamped between 0 and Bars - 1). |
UseTrading |
true | Disables all order generation when set to false, while the channel continues to update. |
Additional notes
- The strategy stores stop and target levels locally because StockSharp market orders do not automatically attach SL/TP levels.
- It works on any instrument that supports minute and hourly candles; however, the original logic was designed for forex pairs.
- Adjust
Barsto match the volatility of the traded instrument. A smaller value reacts faster, a larger value produces smoother channels. - Set
Degreeto 1 for a straight regression channel (closest to the classic linear version), or use higher degrees to emulate the polynomial modes from the MQL indicator.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Multi time frame regression channel strategy.
/// Converts the MQL "Multi Time Frame Trader" logic to StockSharp.
/// </summary>
public class MultiTimeFrameTraderStrategy : Strategy
{
private static readonly DataType M1Type = TimeSpan.FromMinutes(5).TimeFrame();
private static readonly DataType M5Type = TimeSpan.FromHours(1).TimeFrame();
private static readonly DataType H1Type = TimeSpan.FromHours(4).TimeFrame();
private readonly StrategyParam<int> _degree;
private readonly StrategyParam<decimal> _stdMultiplier;
private readonly StrategyParam<int> _bars;
private readonly StrategyParam<int> _shift;
private readonly StrategyParam<bool> _useTrading;
private RegressionChannelState _m1State;
private RegressionChannelState _m5State;
private RegressionChannelState _h1State;
private Sides? _positionSide;
private decimal? _stopPrice;
private decimal? _targetPrice;
/// <summary>
/// Polynomial degree for the regression channel (1-3).
/// </summary>
public int Degree
{
get => _degree.Value;
set => _degree.Value = value;
}
/// <summary>
/// Standard deviation multiplier used to build the channel width.
/// </summary>
public decimal StdMultiplier
{
get => _stdMultiplier.Value;
set => _stdMultiplier.Value = value;
}
/// <summary>
/// Bars used for regression fitting and slope comparison.
/// </summary>
public int Bars
{
get => _bars.Value;
set => _bars.Value = value;
}
/// <summary>
/// Bars to shift the regression evaluation point.
/// </summary>
public int Shift
{
get => _shift.Value;
set => _shift.Value = value;
}
/// <summary>
/// Enables or disables trading logic.
/// </summary>
public bool UseTrading
{
get => _useTrading.Value;
set => _useTrading.Value = value;
}
public MultiTimeFrameTraderStrategy()
{
_degree = Param(nameof(Degree), 1)
.SetGreaterThanZero()
.SetDisplay("Polynomial Degree", "Degree for regression channel", "Regression")
;
_stdMultiplier = Param(nameof(StdMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Std Multiplier", "Standard deviation multiplier", "Regression")
;
_bars = Param(nameof(Bars), 20)
.SetGreaterThanZero()
.SetDisplay("Regression Bars", "Bars for regression and slope", "Regression")
;
_shift = Param(nameof(Shift), 0)
.SetNotNegative()
.SetDisplay("Shift", "Bars to shift regression evaluation", "Regression");
_useTrading = Param(nameof(UseTrading), true)
.SetDisplay("Use Trading", "Enable order execution", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return
[
(Security, M1Type),
(Security, M5Type),
(Security, H1Type)
];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
// Clear manual stop/target tracking when the strategy is reset.
_positionSide = null;
_stopPrice = null;
_targetPrice = null;
_m1State = null;
_m5State = null;
_h1State = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var degree = Math.Max(1, Math.Min(3, Degree));
var bars = Math.Max(1, Bars);
var shift = Math.Max(0, Math.Min(Shift, bars - 1));
var multiplier = Math.Max(0.1m, StdMultiplier);
// Initialize regression states for each time frame.
_m1State = new RegressionChannelState(bars, degree, multiplier, shift);
_m5State = new RegressionChannelState(bars, degree, multiplier, shift);
_h1State = new RegressionChannelState(bars, degree, multiplier, shift);
var m1Subscription = SubscribeCandles(M1Type);
m1Subscription.Bind(ProcessM1).Start();
var m5Subscription = SubscribeCandles(M5Type);
m5Subscription.Bind(ProcessM5).Start();
var h1Subscription = SubscribeCandles(H1Type);
h1Subscription.Bind(ProcessH1).Start();
}
private void ProcessM1(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Update the regression channel with the latest one-minute candle.
_m1State?.Process(candle);
// Manage existing positions before evaluating fresh entry signals.
TryManagePosition(candle);
if (!UseTrading)
return;
if (_m1State == null || _m5State == null || _h1State == null || !_m1State.IsReady || !_m5State.IsReady || !_h1State.IsReady)
return;
var slopeH1 = _h1State.Slope;
if (slopeH1 is null)
return;
var m5Upper = _m5State.Upper;
var m5Middle = _m5State.Middle;
var m5Lower = _m5State.Lower;
var m1Upper = _m1State.Upper;
var m1Lower = _m1State.Lower;
if (m5Upper is null || m5Middle is null || m5Lower is null || m1Upper is null || m1Lower is null)
return;
var m5High = _m5State.High;
var m5Low = _m5State.Low;
var m1High = _m1State.High;
var m1Low = _m1State.Low;
if (m5High is null || m5Low is null || m1High is null || m1Low is null)
return;
// Short setup: higher time frame slope is down and both M5 and M1 touch the resistance band.
if (slopeH1 < 0m && Position >= 0m)
{
if (m5High >= m5Upper && m1High >= m1Upper)
{
var halfWidth = Math.Abs(m5Upper.Value - m5Middle.Value) / 2m;
var stop = candle.ClosePrice + halfWidth;
var target = m5Middle.Value;
EnterShort(stop, target);
return;
}
}
// Long setup: higher time frame slope is up and both M5 and M1 test the support band.
if (slopeH1 > 0m && Position <= 0m)
{
if (m5Low <= m5Lower && m1Low <= m1Lower)
{
var halfWidth = Math.Abs(m5Middle.Value - m5Lower.Value) / 2m;
var stop = candle.ClosePrice - halfWidth;
var target = m5Middle.Value;
EnterLong(stop, target);
}
}
}
private void ProcessM5(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Store the latest five-minute regression data used for confirmations.
_m5State?.Process(candle);
}
private void ProcessH1(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Track the hourly regression slope to define the dominant direction.
_h1State?.Process(candle);
}
private void TryManagePosition(ICandleMessage candle)
{
if (!UseTrading || _positionSide is null)
return;
// For long positions check stop loss first, then the profit target.
if (_positionSide == Sides.Buy)
{
if (_stopPrice is not null && candle.LowPrice <= _stopPrice)
{
ExitLong();
return;
}
if (_targetPrice is not null && candle.HighPrice >= _targetPrice)
ExitLong();
}
// For short positions mirror the stop and target checks.
else if (_positionSide == Sides.Sell)
{
if (_stopPrice is not null && candle.HighPrice >= _stopPrice)
{
ExitShort();
return;
}
if (_targetPrice is not null && candle.LowPrice <= _targetPrice)
ExitShort();
}
}
private void EnterLong(decimal stop, decimal target)
{
// Market entry is issued first, then local stop/target levels are stored.
BuyMarket();
_positionSide = Sides.Buy;
_stopPrice = stop;
_targetPrice = target;
}
private void EnterShort(decimal stop, decimal target)
{
SellMarket();
_positionSide = Sides.Sell;
_stopPrice = stop;
_targetPrice = target;
}
private void ExitLong()
{
SellMarket();
// Reset tracking so a new setup can be processed immediately.
_positionSide = null;
_stopPrice = null;
_targetPrice = null;
}
private void ExitShort()
{
BuyMarket();
_positionSide = null;
_stopPrice = null;
_targetPrice = null;
}
private sealed class RegressionChannelState
{
private readonly int _length;
private readonly int _degree;
private readonly decimal _multiplier;
private readonly int _shift;
private readonly List<decimal> _closes = new();
private readonly List<decimal> _upperHistory = new();
public decimal? Upper { get; private set; }
public decimal? Middle { get; private set; }
public decimal? Lower { get; private set; }
public decimal? Slope { get; private set; }
public decimal? High { get; private set; }
public decimal? Low { get; private set; }
public bool IsReady { get; private set; }
public RegressionChannelState(int length, int degree, decimal multiplier, int shift)
{
_length = length;
_degree = Math.Max(1, Math.Min(3, degree));
_multiplier = multiplier;
_shift = shift;
}
public void Process(ICandleMessage candle)
{
High = candle.HighPrice;
Low = candle.LowPrice;
_closes.Add(candle.ClosePrice);
if (_closes.Count > _length)
try { _closes.RemoveAt(0); } catch { }
if (_closes.Count < _length)
{
IsReady = false;
Upper = null;
Middle = null;
Lower = null;
Slope = null;
return;
}
var values = _closes.ToArray();
var coeffs = PolyFit(values, _degree);
var index = values.Length - 1 - Math.Min(_shift, values.Length - 1);
var mid = PolyEval(coeffs, index);
decimal sumSquares = 0m;
for (var i = 0; i < values.Length; i++)
{
var estimate = PolyEval(coeffs, i);
var diff = values[i] - estimate;
sumSquares += diff * diff;
}
var std = (decimal)Math.Sqrt((double)(sumSquares / values.Length));
var upper = mid + std * _multiplier;
var lower = mid - std * _multiplier;
_upperHistory.Add(upper);
if (_upperHistory.Count > _length + 1)
try { _upperHistory.RemoveAt(0); } catch { }
decimal? slope = null;
if (_upperHistory.Count > _length)
slope = upper - _upperHistory[0];
Upper = upper;
Middle = mid;
Lower = lower;
Slope = slope;
IsReady = true;
}
private static decimal[] PolyFit(IReadOnlyList<decimal> values, int degree)
{
var n = values.Count;
var order = Math.Min(degree, n - 1);
var size = order + 1;
var matrix = new decimal[size, size + 1];
for (var row = 0; row < size; row++)
{
for (var col = 0; col < size; col++)
{
decimal sum = 0m;
for (var i = 0; i < n; i++)
sum += (decimal)Math.Pow(i, row + col);
matrix[row, col] = sum;
}
decimal sumY = 0m;
for (var i = 0; i < n; i++)
sumY += values[i] * (decimal)Math.Pow(i, row);
matrix[row, size] = sumY;
}
for (var i = 0; i < size; i++)
{
if (matrix[i, i] == 0m)
{
var swapRow = i + 1;
while (swapRow < size && matrix[swapRow, i] == 0m)
swapRow++;
if (swapRow < size)
SwapRows(matrix, i, swapRow, size + 1);
}
var pivot = matrix[i, i];
if (pivot == 0m)
continue;
for (var j = i; j < size + 1; j++)
matrix[i, j] /= pivot;
for (var k = 0; k < size; k++)
{
if (k == i)
continue;
var factor = matrix[k, i];
if (factor == 0m)
continue;
for (var j = i; j < size + 1; j++)
matrix[k, j] -= factor * matrix[i, j];
}
}
var coeffs = new decimal[size];
for (var i = 0; i < size; i++)
coeffs[i] = matrix[i, size];
return coeffs;
}
private static void SwapRows(decimal[,] matrix, int a, int b, int width)
{
for (var col = 0; col < width; col++)
{
(matrix[a, col], matrix[b, col]) = (matrix[b, col], matrix[a, col]);
}
}
private static decimal PolyEval(IReadOnlyList<decimal> coeffs, int x)
{
decimal y = 0m;
decimal power = 1m;
for (var i = 0; i < coeffs.Count; i++)
{
y += coeffs[i] * power;
power *= x;
}
return y;
}
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class _RegressionChannelState(object):
"""Internal regression channel calculator."""
def __init__(self, length, degree, multiplier, shift):
self._length = length
self._degree = max(1, min(3, degree))
self._multiplier = multiplier
self._shift = shift
self._closes = []
self._upper_history = []
self.Upper = None
self.Middle = None
self.Lower = None
self.Slope = None
self.High = None
self.Low = None
self.IsReady = False
def process(self, candle):
self.High = float(candle.HighPrice)
self.Low = float(candle.LowPrice)
self._closes.append(float(candle.ClosePrice))
if len(self._closes) > self._length:
self._closes.pop(0)
if len(self._closes) < self._length:
self.IsReady = False
self.Upper = None
self.Middle = None
self.Lower = None
self.Slope = None
return
values = self._closes[:]
coeffs = self._poly_fit(values, self._degree)
index = len(values) - 1 - min(self._shift, len(values) - 1)
mid = self._poly_eval(coeffs, index)
sum_sq = 0.0
for i in range(len(values)):
estimate = self._poly_eval(coeffs, i)
diff = values[i] - estimate
sum_sq += diff * diff
std = math.sqrt(sum_sq / len(values))
upper = mid + std * self._multiplier
lower = mid - std * self._multiplier
self._upper_history.append(upper)
if len(self._upper_history) > self._length + 1:
self._upper_history.pop(0)
slope = None
if len(self._upper_history) > self._length:
slope = upper - self._upper_history[0]
self.Upper = upper
self.Middle = mid
self.Lower = lower
self.Slope = slope
self.IsReady = True
def _poly_fit(self, values, degree):
n = len(values)
order = min(degree, n - 1)
size = order + 1
# Build augmented matrix
matrix = [[0.0] * (size + 1) for _ in range(size)]
for row in range(size):
for col in range(size):
s = 0.0
for i in range(n):
s += i ** (row + col)
matrix[row][col] = s
sy = 0.0
for i in range(n):
sy += values[i] * (i ** row)
matrix[row][size] = sy
# Gauss-Jordan elimination
for i in range(size):
if matrix[i][i] == 0:
swap_row = i + 1
while swap_row < size and matrix[swap_row][i] == 0:
swap_row += 1
if swap_row < size:
matrix[i], matrix[swap_row] = matrix[swap_row], matrix[i]
pivot = matrix[i][i]
if pivot == 0:
continue
for j in range(i, size + 1):
matrix[i][j] /= pivot
for k in range(size):
if k == i:
continue
factor = matrix[k][i]
if factor == 0:
continue
for j in range(i, size + 1):
matrix[k][j] -= factor * matrix[i][j]
return [matrix[i][size] for i in range(size)]
def _poly_eval(self, coeffs, x):
y = 0.0
power = 1.0
for c in coeffs:
y += c * power
power *= x
return y
class multi_time_frame_trader_strategy(Strategy):
"""Multi time frame regression channel strategy with 3 timeframes."""
def __init__(self):
super(multi_time_frame_trader_strategy, self).__init__()
self._degree = self.Param("Degree", 1) \
.SetGreaterThanZero() \
.SetDisplay("Polynomial Degree", "Degree for regression channel", "Regression")
self._std_multiplier = self.Param("StdMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Std Multiplier", "Standard deviation multiplier", "Regression")
self._bars = self.Param("Bars", 20) \
.SetGreaterThanZero() \
.SetDisplay("Regression Bars", "Bars for regression and slope", "Regression")
self._shift = self.Param("Shift", 0) \
.SetDisplay("Shift", "Bars to shift regression evaluation", "Regression")
self._use_trading = self.Param("UseTrading", True) \
.SetDisplay("Use Trading", "Enable order execution", "Trading")
self._m1_type = DataType.TimeFrame(TimeSpan.FromMinutes(5))
self._m5_type = DataType.TimeFrame(TimeSpan.FromHours(1))
self._h1_type = DataType.TimeFrame(TimeSpan.FromHours(4))
self._m1_state = None
self._m5_state = None
self._h1_state = None
self._position_side = None # 'buy' or 'sell'
self._stop_price = None
self._target_price = None
@property
def Degree(self):
return int(self._degree.Value)
@property
def StdMultiplier(self):
return float(self._std_multiplier.Value)
@property
def Bars(self):
return int(self._bars.Value)
@property
def Shift(self):
return int(self._shift.Value)
@property
def UseTrading(self):
return self._use_trading.Value
def OnStarted2(self, time):
super(multi_time_frame_trader_strategy, self).OnStarted2(time)
degree = max(1, min(3, self.Degree))
bars = max(1, self.Bars)
shift = max(0, min(self.Shift, bars - 1))
multiplier = max(0.1, self.StdMultiplier)
self._m1_state = _RegressionChannelState(bars, degree, multiplier, shift)
self._m5_state = _RegressionChannelState(bars, degree, multiplier, shift)
self._h1_state = _RegressionChannelState(bars, degree, multiplier, shift)
self._position_side = None
self._stop_price = None
self._target_price = None
m1_sub = self.SubscribeCandles(self._m1_type)
m1_sub.Bind(self._process_m1).Start()
m5_sub = self.SubscribeCandles(self._m5_type)
m5_sub.Bind(self._process_m5).Start()
h1_sub = self.SubscribeCandles(self._h1_type)
h1_sub.Bind(self._process_h1).Start()
def _process_m1(self, candle):
if candle.State != CandleStates.Finished:
return
self._m1_state.process(candle)
self._try_manage_position(candle)
if not self.UseTrading:
return
if self._m1_state is None or self._m5_state is None or self._h1_state is None:
return
if not self._m1_state.IsReady or not self._m5_state.IsReady or not self._h1_state.IsReady:
return
slope_h1 = self._h1_state.Slope
if slope_h1 is None:
return
m5_upper = self._m5_state.Upper
m5_middle = self._m5_state.Middle
m5_lower = self._m5_state.Lower
m1_upper = self._m1_state.Upper
m1_lower = self._m1_state.Lower
if m5_upper is None or m5_middle is None or m5_lower is None or m1_upper is None or m1_lower is None:
return
m5_high = self._m5_state.High
m5_low = self._m5_state.Low
m1_high = self._m1_state.High
m1_low = self._m1_state.Low
if m5_high is None or m5_low is None or m1_high is None or m1_low is None:
return
close = float(candle.ClosePrice)
# Short setup
if slope_h1 < 0 and self.Position >= 0:
if m5_high >= m5_upper and m1_high >= m1_upper:
half_width = abs(m5_upper - m5_middle) / 2.0
stop = close + half_width
target = m5_middle
self._enter_short(stop, target)
return
# Long setup
if slope_h1 > 0 and self.Position <= 0:
if m5_low <= m5_lower and m1_low <= m1_lower:
half_width = abs(m5_middle - m5_lower) / 2.0
stop = close - half_width
target = m5_middle
self._enter_long(stop, target)
def _process_m5(self, candle):
if candle.State != CandleStates.Finished:
return
self._m5_state.process(candle)
def _process_h1(self, candle):
if candle.State != CandleStates.Finished:
return
self._h1_state.process(candle)
def _try_manage_position(self, candle):
if not self.UseTrading or self._position_side is None:
return
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self._position_side == 'buy':
if self._stop_price is not None and lo <= self._stop_price:
self._exit_long()
return
if self._target_price is not None and h >= self._target_price:
self._exit_long()
elif self._position_side == 'sell':
if self._stop_price is not None and h >= self._stop_price:
self._exit_short()
return
if self._target_price is not None and lo <= self._target_price:
self._exit_short()
def _enter_long(self, stop, target):
self.BuyMarket()
self._position_side = 'buy'
self._stop_price = stop
self._target_price = target
def _enter_short(self, stop, target):
self.SellMarket()
self._position_side = 'sell'
self._stop_price = stop
self._target_price = target
def _exit_long(self):
self.SellMarket()
self._position_side = None
self._stop_price = None
self._target_price = None
def _exit_short(self):
self.BuyMarket()
self._position_side = None
self._stop_price = None
self._target_price = None
def OnReseted(self):
super(multi_time_frame_trader_strategy, self).OnReseted()
self._position_side = None
self._stop_price = None
self._target_price = None
self._m1_state = None
self._m5_state = None
self._h1_state = None
def CreateClone(self):
return multi_time_frame_trader_strategy()