This breakout strategy places stop orders at the start of each trading day. It measures the average daily range over a configurable number of days and uses that value to derive stop-loss and take-profit levels. Orders are positioned on both sides of the current price and only one side is expected to trigger.
At the specified OpenHour the strategy calculates buy and sell stop prices at half of the stop-loss distance from the current market price. The stop-loss and take-profit levels are defined as percentages of the average range. When one stop order is filled the opposite order can either be cancelled or kept for position reversal. An optional martingale feature multiplies the volume of the remaining order after a fill.
Any pending entry orders that remain unfilled by CloseHour are removed to avoid overnight exposure. After an entry the strategy immediately places protective stop-loss and take-profit orders relative to the fill price.
Details
Entry Criteria:
Calculate average daily range using an ATR over VolatilityDays days.
Compute stop-loss and take-profit distances as StopLossRate and TakeProfitRate percent of that range.
At OpenHour place buy and sell stop orders offset = stopLoss/2 away from market price.
Exit Criteria:
Protective stop-loss and take-profit orders close positions.
Pending entry orders are cancelled at CloseHour.
Reverse Mode:
If Reverse is true the opposite stop order remains to reverse the position.
If UseMartingale is also true the remaining order is re-registered with volume multiplied by MartingaleMultiplier.
Long/Short: Both directions.
Stops: Fixed stop-loss and take-profit based on daily range.
Default Values:
VolatilityDays = 5
OpenHour = 7
CloseHour = 10
StopLossRate = 15%
TakeProfitRate = 30%
Reverse = false
UseMartingale = false
MartingaleMultiplier = 2.0
This approach attempts to capture breakouts after quiet overnight sessions while limiting risk through volatility-adjusted targets.
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>
/// SAW System breakout strategy.
/// Uses ATR to calculate volatility range, then enters on breakout above/below
/// the open price offset by a fraction of ATR.
/// </summary>
public class SawSystem1Strategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _breakoutMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevAtr;
private decimal _sessionOpen;
private bool _traded;
private DateTime _currentDate;
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public decimal BreakoutMultiplier
{
get => _breakoutMultiplier.Value;
set => _breakoutMultiplier.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public SawSystem1Strategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators");
_breakoutMultiplier = Param(nameof(BreakoutMultiplier), 0.5m)
.SetDisplay("Breakout Multiplier", "Fraction of ATR for breakout offset", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevAtr = null;
_sessionOpen = 0;
_traded = false;
_currentDate = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevAtr = null;
_sessionOpen = 0;
_traded = false;
_currentDate = default;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var date = candle.OpenTime.Date;
// New day: record open price and reset
if (date != _currentDate)
{
_currentDate = date;
_sessionOpen = candle.OpenPrice;
_traded = false;
// Close any open position at start of new day
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
_prevAtr = atrValue;
return;
}
if (_traded || _prevAtr is null || _sessionOpen == 0)
{
_prevAtr = atrValue;
return;
}
var offset = _prevAtr.Value * BreakoutMultiplier;
var upperBreak = _sessionOpen + offset;
var lowerBreak = _sessionOpen - offset;
if (candle.ClosePrice > upperBreak && Position <= 0)
{
BuyMarket();
_traded = true;
}
else if (candle.ClosePrice < lowerBreak && Position >= 0)
{
SellMarket();
_traded = true;
}
_prevAtr = atrValue;
}
}
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 AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class saw_system1_strategy(Strategy):
def __init__(self):
super(saw_system1_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
self._breakout_multiplier = self.Param("BreakoutMultiplier", 0.5) \
.SetDisplay("Breakout Multiplier", "Fraction of ATR for breakout offset", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_atr = None
self._session_open = 0.0
self._traded = False
self._current_date = None
@property
def atr_period(self):
return self._atr_period.Value
@property
def breakout_multiplier(self):
return self._breakout_multiplier.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(saw_system1_strategy, self).OnReseted()
self._prev_atr = None
self._session_open = 0.0
self._traded = False
self._current_date = None
def OnStarted2(self, time):
super(saw_system1_strategy, self).OnStarted2(time)
self._prev_atr = None
self._session_open = 0.0
self._traded = False
self._current_date = None
atr = AverageTrueRange()
atr.Length = self.atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
atr_value = float(atr_value)
date = candle.OpenTime.Date
if self._current_date is None or date != self._current_date:
self._current_date = date
self._session_open = float(candle.OpenPrice)
self._traded = False
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._prev_atr = atr_value
return
if self._traded or self._prev_atr is None or self._session_open == 0.0:
self._prev_atr = atr_value
return
offset = self._prev_atr * float(self.breakout_multiplier)
upper_break = self._session_open + offset
lower_break = self._session_open - offset
close_price = float(candle.ClosePrice)
if close_price > upper_break and self.Position <= 0:
self.BuyMarket()
self._traded = True
elif close_price < lower_break and self.Position >= 0:
self.SellMarket()
self._traded = True
self._prev_atr = atr_value
def CreateClone(self):
return saw_system1_strategy()