The Ma Channel strategy trades breakouts of a moving average channel built from the
high and low prices. A position is opened when price leaves the channel in the
corresponding direction and reversed when the trend flips. The channel boundaries
are calculated from exponential moving averages with a fixed offset.
The system is designed for both long and short trading and reacts only on finished
candles. It aims to catch trend transitions early while avoiding noise inside the
channel.
Details
Entry Conditions:
Long: Price breaks above the upper channel.
Short: Price breaks below the lower channel.
Exit Conditions:
Opposite breakout triggers a reversal of the position.
Indicators: Exponential moving averages of highs and lows with configurable
length and price offset.
Stops: Not used by default, trades are closed only on opposite signals.
Default Values:
Length = 8
Offset = 10
CandleType = 1 hour candles
Filters:
Category: Trend following
Direction: Both
Indicators: Single
Stops: No
Complexity: Simple
Timeframe: Medium
Seasonality: No
Neural networks: No
Divergence: No
Risk level: Moderate
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;
/// <summary>
/// Moving average channel breakout strategy.
/// Buys when price crosses above the upper channel (MA of highs + offset).
/// Sells when price crosses below the lower channel (MA of lows - offset).
/// </summary>
public class MaChannelStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _offset;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _maHigh;
private ExponentialMovingAverage _maLow;
private int _trend;
public int Length { get => _length.Value; set => _length.Value = value; }
public decimal Offset { get => _offset.Value; set => _offset.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MaChannelStrategy()
{
_length = Param(nameof(Length), 8)
.SetDisplay("Length", "Moving average period", "Parameters")
.SetOptimize(5, 20, 1);
_offset = Param(nameof(Offset), 100m)
.SetDisplay("Offset", "Price offset from the average", "Parameters")
.SetOptimize(50m, 500m, 50m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_maHigh = null;
_maLow = null;
_trend = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_trend = 0;
_maHigh = new ExponentialMovingAverage { Length = Length };
_maLow = new ExponentialMovingAverage { Length = Length };
Indicators.Add(_maHigh);
Indicators.Add(_maLow);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var t = candle.ServerTime;
var highResult = _maHigh.Process(new DecimalIndicatorValue(_maHigh, candle.HighPrice, t) { IsFinal = true });
var lowResult = _maLow.Process(new DecimalIndicatorValue(_maLow, candle.LowPrice, t) { IsFinal = true });
if (!_maHigh.IsFormed || !_maLow.IsFormed)
return;
var upper = highResult.GetValue<decimal>() + Offset;
var lower = lowResult.GetValue<decimal>() - Offset;
var prevTrend = _trend;
if (candle.HighPrice > upper)
_trend = 1;
else if (candle.LowPrice < lower)
_trend = -1;
if (prevTrend <= 0 && _trend > 0 && Position <= 0)
BuyMarket();
else if (prevTrend >= 0 && _trend < 0 && Position >= 0)
SellMarket();
}
}
import clr
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class ma_channel_strategy(Strategy):
def __init__(self):
super(ma_channel_strategy, self).__init__()
self._length = self.Param("Length", 8) \
.SetDisplay("Length", "Moving average period", "Parameters")
self._offset = self.Param("Offset", 100.0) \
.SetDisplay("Offset", "Price offset from the average", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "Parameters")
self._ma_high = None
self._ma_low = None
self._trend = 0
@property
def length(self):
return self._length.Value
@property
def offset(self):
return self._offset.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_channel_strategy, self).OnReseted()
self._ma_high = None
self._ma_low = None
self._trend = 0
def OnStarted2(self, time):
super(ma_channel_strategy, self).OnStarted2(time)
self._trend = 0
self._ma_high = ExponentialMovingAverage()
self._ma_high.Length = self.length
self._ma_low = ExponentialMovingAverage()
self._ma_low.Length = self.length
self.Indicators.Add(self._ma_high)
self.Indicators.Add(self._ma_low)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
t = candle.ServerTime
high_result = process_float(self._ma_high, float(candle.HighPrice), t, True)
low_result = process_float(self._ma_low, float(candle.LowPrice), t, True)
if not self._ma_high.IsFormed or not self._ma_low.IsFormed:
return
offset_val = float(self.offset)
upper = float(high_result) + offset_val
lower = float(low_result) - offset_val
prev_trend = self._trend
high_price = float(candle.HighPrice)
low_price = float(candle.LowPrice)
if high_price > upper:
self._trend = 1
elif low_price < lower:
self._trend = -1
if prev_trend <= 0 and self._trend > 0 and self.Position <= 0:
self.BuyMarket()
elif prev_trend >= 0 and self._trend < 0 and self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return ma_channel_strategy()