This strategy exploits breakouts relative to a Triangular Moving Average (TMA). It watches a configurable candle series and compares the previous candle's close to the TMA value plus or minus user defined offsets. A long position is opened when the previous close is above TMA + UpLevel, and a short position is opened when it is below TMA - DownLevel. Opposite signals reverse the position.
Parameters
TMA Length – period used to calculate the Triangular Moving Average.
Upper Level – price offset added to the TMA to detect long signals.
Lower Level – price offset subtracted from the TMA to detect short signals.
Candle Type – timeframe of candles used by the strategy.
How it works
Subscribes to the selected candle series.
Binds a Triangular Moving Average indicator to the candles.
On each finished candle it:
Stores the previous TMA and close values.
Checks if the previous close exceeded the upper or lower level.
Sends market orders to open or reverse positions accordingly.
Charts candles, indicator line and own trades for visual analysis.
Notes
The strategy uses market orders without stop-loss or take-profit management. It is intended for educational purposes and should be extended with proper risk controls before live trading.
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>
/// Strategy that enters when price breaks the Triangular Moving Average by configurable offsets.
/// </summary>
public class TmaBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _upLevel;
private readonly StrategyParam<decimal> _downLevel;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevTma;
private decimal? _prevClose;
/// <summary>
/// Period for the Triangular Moving Average.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Offset above the TMA to trigger a long entry.
/// </summary>
public decimal UpLevel
{
get => _upLevel.Value;
set => _upLevel.Value = value;
}
/// <summary>
/// Offset below the TMA to trigger a short entry.
/// </summary>
public decimal DownLevel
{
get => _downLevel.Value;
set => _downLevel.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize the TMA breakout strategy.
/// </summary>
public TmaBreakoutStrategy()
{
_length = Param(nameof(Length), 30)
.SetDisplay("TMA Length", "Period for the Triangular Moving Average", "Parameters")
.SetOptimize(10, 60, 10);
_upLevel = Param(nameof(UpLevel), 300m)
.SetDisplay("Upper Level", "Offset above TMA in price units", "Parameters")
.SetOptimize(100m, 500m, 100m);
_downLevel = Param(nameof(DownLevel), 300m)
.SetDisplay("Lower Level", "Offset below TMA in price units", "Parameters")
.SetOptimize(100m, 500m, 100m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevTma = _prevClose = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var tma = new ExponentialMovingAverage { Length = Length };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(tma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, tma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal tmaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var prevTma = _prevTma;
var prevClose = _prevClose;
if (prevTma is null || prevClose is null)
{
_prevTma = tmaValue;
_prevClose = candle.ClosePrice;
return;
}
var signalUp = prevClose > prevTma + UpLevel;
var signalDn = prevClose < prevTma - DownLevel;
if (signalUp && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (signalDn && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevTma = tmaValue;
_prevClose = candle.ClosePrice;
}
}
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
class tma_breakout_strategy(Strategy):
def __init__(self):
super(tma_breakout_strategy, self).__init__()
self._length = self.Param("Length", 30) \
.SetDisplay("TMA Length", "Period for the Triangular Moving Average", "Parameters")
self._up_level = self.Param("UpLevel", 300.0) \
.SetDisplay("Upper Level", "Offset above TMA in price units", "Parameters")
self._down_level = self.Param("DownLevel", 300.0) \
.SetDisplay("Lower Level", "Offset below TMA in price units", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_tma = None
self._prev_close = None
@property
def length(self):
return self._length.Value
@property
def up_level(self):
return self._up_level.Value
@property
def down_level(self):
return self._down_level.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(tma_breakout_strategy, self).OnReseted()
self._prev_tma = None
self._prev_close = None
def OnStarted2(self, time):
super(tma_breakout_strategy, self).OnStarted2(time)
tma = ExponentialMovingAverage()
tma.Length = self.length
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(tma, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, tma)
self.DrawOwnTrades(area)
def process_candle(self, candle, tma_value):
if candle.State != CandleStates.Finished:
return
tma_val = float(tma_value)
close = float(candle.ClosePrice)
if self._prev_tma is None or self._prev_close is None:
self._prev_tma = tma_val
self._prev_close = close
return
signal_up = self._prev_close > self._prev_tma + float(self.up_level)
signal_dn = self._prev_close < self._prev_tma - float(self.down_level)
if signal_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif signal_dn and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_tma = tma_val
self._prev_close = close
def CreateClone(self):
return tma_breakout_strategy()