SV Daily Breakout Strategy
Overview
The SV Daily Breakout Strategy is a direct C# conversion of the “SV v.4.2.5” MetaTrader 5 expert advisor. The system evaluates price action once per completed bar and allows at most one trade per exchange day. Trading begins only after the configured start time and relies on the relationship between the recent high/low range and two smoothed moving averages. A long position is opened when the full analysed range stays below both averages, signalling an anticipated rebound from oversold conditions. Conversely, a short position is opened when the range remains above both averages, signalling a potential reversal from overbought territory.
Trading Rules
Entry conditions
- Daily gate – no trades are evaluated until the current server time is later than Start Hour/Start Minute. Only one entry is permitted per day.
- Data window – the strategy skips the most recent
Shiftbars and analyses the nextIntervalbars. Their highest and lowest prices are compared against the shifted moving averages. - Long entry – if the highest price in the analysed window is strictly below the slow MA and the lowest price is strictly below the fast MA, enter long (closing any existing short position first).
- Short entry – if the lowest price in the analysed window is strictly above the slow MA and the highest price is strictly above the fast MA, enter short (closing any existing long position first).
Exit management
- Initial stop loss – placed
Stop Loss (pips)away from the entry price. If the level is hit, the position is closed. - Take profit – placed
Take Profit (pips)away from the entry price. If the level is hit, the position is closed. - Trailing stop – when enabled (both trailing distance and step are greater than zero), the stop moves in the direction of profit. For longs the stop is raised to
Close − Trailing Stoponce price advances more thanTrailing Stop + Trailing Step; shorts mirror the logic. - Daily lockout – regardless of how a trade exits, the strategy will not open a new position until the next trading day.
Position sizing
- Manual mode – when Use Manual Volume is
true, the strategy sends the fixed Volume value (adjusted to the instrument volume step). - Risk-based mode – when Use Manual Volume is
false, the strategy estimates the trade size from account equity andRisk %. It divides the risk capital by the monetary value of the configured stop distance, using instrument step information when available.
Parameters
| Parameter | Default | Description |
|---|---|---|
| Use Manual Volume | false |
Use the fixed Volume value instead of risk-based sizing. |
| Volume | 0.1 |
Trade volume when manual sizing is enabled. |
| Risk % | 5 |
Percentage of account equity risked per trade when manual sizing is active. |
| Stop Loss (pips) | 50 |
Stop-loss distance in pips. Set to 0 to disable. |
| Take Profit (pips) | 50 |
Take-profit distance in pips. Set to 0 to disable. |
| Trailing Stop (pips) | 5 |
Trailing stop distance in pips. Requires Trailing Step to be greater than zero. |
| Trailing Step (pips) | 5 |
Minimal profit increment before the trailing stop is moved. |
| Start Hour | 19 |
Hour (exchange time) when entries may start. |
| Start Minute | 0 |
Minute (exchange time) when entries may start. |
| Shift | 6 |
Number of newest bars excluded before analysing the range. |
| Interval | 27 |
Number of historical bars used to compute the high/low window. |
| Fast MA Period | 14 |
Length of the fast moving average. |
| Fast MA Shift | 0 |
Horizontal shift (bars ago) used for the fast MA value. |
| Fast MA Method | Smma |
Moving average method for the fast MA. |
| Fast Applied Price | Median |
Price source for the fast MA. |
| Slow MA Period | 41 |
Length of the slow moving average. |
| Slow MA Shift | 0 |
Horizontal shift (bars ago) used for the slow MA value. |
| Slow MA Method | Smma |
Moving average method for the slow MA. |
| Slow Applied Price | Median |
Price source for the slow MA. |
| Candle Type | 1 hour |
Candle series used for calculations. |
Additional Notes
- The conversion keeps the original behaviour of analysing a delayed price window (
Shift+Interval) to avoid the most recent bars when determining breakouts. - Trailing logic uses the candle close price to approximate MetaTrader’s tick-based trailing updates. Adjust the pip distances if your instrument requires different precision.
- Risk-based sizing relies on
Security.PriceStep,Security.StepPrice, andSecurity.VolumeStep. Provide these values in your instrument settings for accurate lot sizing. - The strategy calls
StartProtection()so you can attach additional global risk rules if needed. - To mirror the original EA, make sure your data feed and trading account operate on the same server time zone referenced by the Start Hour and Start Minute parameters.
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>
/// Daily breakout strategy converted from the "SV v.4.2.5" MetaTrader 5 expert advisor.
/// Evaluates one trade per day after a configurable start time using moving average filters.
/// </summary>
public class SvDailyBreakoutStrategy : Strategy
{
private readonly StrategyParam<bool> _useManualVolume;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _startMinute;
private readonly StrategyParam<int> _shift;
private readonly StrategyParam<int> _interval;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _fastMaShift;
private readonly StrategyParam<MovingAverageMethods> _fastMaMethod;
private readonly StrategyParam<AppliedPrices> _fastAppliedPrice;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<int> _slowMaShift;
private readonly StrategyParam<MovingAverageMethods> _slowMaMethod;
private readonly StrategyParam<AppliedPrices> _slowAppliedPrice;
private readonly StrategyParam<DataType> _candleType;
private IIndicator _fastMa;
private IIndicator _slowMa;
private readonly List<decimal> _fastMaValues = new();
private readonly List<decimal> _slowMaValues = new();
private readonly List<decimal> _highHistory = new();
private readonly List<decimal> _lowHistory = new();
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takeProfitPrice;
private decimal? _trailingStopPrice;
private DateTime? _currentDay;
private decimal _pipSize;
/// <summary>
/// Use manual volume instead of the risk-based sizing model.
/// </summary>
public bool UseManualVolume
{
get => _useManualVolume.Value;
set => _useManualVolume.Value = value;
}
/// <summary>
/// Risk percentage of account equity used when calculating the dynamic position size.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Stop loss distance expressed in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance expressed in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Trailing step distance expressed in pips.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Hour of the day (exchange time) when the strategy starts searching for entries.
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Minute of the hour when the strategy starts searching for entries.
/// </summary>
public int StartMinute
{
get => _startMinute.Value;
set => _startMinute.Value = value;
}
/// <summary>
/// Number of recent bars excluded from the high/low analysis window.
/// </summary>
public int Shift
{
get => _shift.Value;
set => _shift.Value = value;
}
/// <summary>
/// Number of bars that are analysed when computing the breakout range.
/// </summary>
public int Interval
{
get => _interval.Value;
set => _interval.Value = value;
}
/// <summary>
/// Fast moving average period.
/// </summary>
public int FastMaPeriod
{
get => _fastMaPeriod.Value;
set => _fastMaPeriod.Value = value;
}
/// <summary>
/// Fast moving average horizontal shift.
/// </summary>
public int FastMaShift
{
get => _fastMaShift.Value;
set => _fastMaShift.Value = value;
}
/// <summary>
/// Fast moving average calculation method.
/// </summary>
public MovingAverageMethods FastMaMethod
{
get => _fastMaMethod.Value;
set => _fastMaMethod.Value = value;
}
/// <summary>
/// Applied price used for the fast moving average.
/// </summary>
public AppliedPrices FastAppliedPrice
{
get => _fastAppliedPrice.Value;
set => _fastAppliedPrice.Value = value;
}
/// <summary>
/// Slow moving average period.
/// </summary>
public int SlowMaPeriod
{
get => _slowMaPeriod.Value;
set => _slowMaPeriod.Value = value;
}
/// <summary>
/// Slow moving average horizontal shift.
/// </summary>
public int SlowMaShift
{
get => _slowMaShift.Value;
set => _slowMaShift.Value = value;
}
/// <summary>
/// Slow moving average calculation method.
/// </summary>
public MovingAverageMethods SlowMaMethod
{
get => _slowMaMethod.Value;
set => _slowMaMethod.Value = value;
}
/// <summary>
/// Applied price used for the slow moving average.
/// </summary>
public AppliedPrices SlowAppliedPrice
{
get => _slowAppliedPrice.Value;
set => _slowAppliedPrice.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters with defaults that match the original expert advisor.
/// </summary>
public SvDailyBreakoutStrategy()
{
_useManualVolume = Param(nameof(UseManualVolume), false)
.SetDisplay("Use Manual Volume", "Use fixed volume instead of risk percentage", "Risk");
_riskPercent = Param(nameof(RiskPercent), 5m)
.SetGreaterThanZero()
.SetDisplay("Risk %", "Risk percentage of account equity", "Risk");
_stopLossPips = Param(nameof(StopLossPips), 50)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Stop loss distance in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 50)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Take profit distance in pips", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 5)
.SetNotNegative()
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetNotNegative()
.SetDisplay("Trailing Step (pips)", "Trailing step increment in pips", "Risk");
_startHour = Param(nameof(StartHour), 0)
.SetDisplay("Start Hour", "Hour when trading may begin", "Trading Window");
_startMinute = Param(nameof(StartMinute), 0)
.SetDisplay("Start Minute", "Minute when trading may begin", "Trading Window");
_shift = Param(nameof(Shift), 2)
.SetNotNegative()
.SetDisplay("Shift", "Number of newest bars excluded from range analysis", "Logic");
_interval = Param(nameof(Interval), 10)
.SetGreaterThanZero()
.SetDisplay("Interval", "Number of historical bars analysed", "Logic");
_fastMaPeriod = Param(nameof(FastMaPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Fast MA Period", "Fast moving average length", "Indicators");
_fastMaShift = Param(nameof(FastMaShift), 0)
.SetNotNegative()
.SetDisplay("Fast MA Shift", "Horizontal shift for the fast moving average", "Indicators");
_fastMaMethod = Param(nameof(FastMaMethod), MovingAverageMethods.Ema)
.SetDisplay("Fast MA Method", "Calculation method for the fast moving average", "Indicators");
_fastAppliedPrice = Param(nameof(FastAppliedPrice), AppliedPrices.Median)
.SetDisplay("Fast Applied Price", "Price type used for the fast moving average", "Indicators");
_slowMaPeriod = Param(nameof(SlowMaPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Slow MA Period", "Slow moving average length", "Indicators");
_slowMaShift = Param(nameof(SlowMaShift), 0)
.SetNotNegative()
.SetDisplay("Slow MA Shift", "Horizontal shift for the slow moving average", "Indicators");
_slowMaMethod = Param(nameof(SlowMaMethod), MovingAverageMethods.Ema)
.SetDisplay("Slow MA Method", "Calculation method for the slow moving average", "Indicators");
_slowAppliedPrice = Param(nameof(SlowAppliedPrice), AppliedPrices.Median)
.SetDisplay("Slow Applied Price", "Price type used for the slow moving average", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle series used for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastMaValues.Clear();
_slowMaValues.Clear();
_highHistory.Clear();
_lowHistory.Clear();
_entryPrice = null;
_pipSize = 0m;
_stopPrice = null;
_takeProfitPrice = null;
_trailingStopPrice = null;
_currentDay = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// validation removed for flexibility
_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
var decimals = Security?.Decimals ?? 2;
var step = Security?.PriceStep ?? 0.01m;
var factor = decimals is 3 or 5 ? 10m : 1m;
_pipSize = step * factor;
if (_pipSize <= 0m)
_pipSize = step > 0m ? step : 0.01m;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastMa, _slowMa, ProcessCandleWithMa)
.Start();
}
private void ProcessCandleWithMa(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateDailyState(candle.CloseTime);
UpdateRangeHistory(candle);
UpdateTrailing(candle);
if (CheckProtectiveExits(candle))
return;
if (Position != 0)
return;
if (!TryGetRangeExtremes(out var lowest, out var highest))
return;
if (highest < slowValue && lowest < fastValue)
{
EnterPosition(true, candle);
return;
}
if (lowest > slowValue && highest > fastValue)
{
EnterPosition(false, candle);
}
}
private void EnterPosition(bool isLong, ICandleMessage candle)
{
var entryPrice = candle.ClosePrice;
var stopDistance = StopLossPips > 0 ? StopLossPips * _pipSize : 0m;
var volume = UseManualVolume
? NormalizeVolume(Volume)
: CalculateRiskBasedVolume(stopDistance);
if (volume <= 0m)
return;
if (isLong)
{
var totalVolume = volume + (Position < 0 ? Math.Abs(Position) : 0m);
if (totalVolume <= 0m)
return;
BuyMarket(totalVolume);
_entryPrice = entryPrice;
_stopPrice = StopLossPips > 0 ? entryPrice - stopDistance : null;
_takeProfitPrice = TakeProfitPips > 0 ? entryPrice + TakeProfitPips * _pipSize : null;
}
else
{
var totalVolume = volume + (Position > 0 ? Position : 0m);
if (totalVolume <= 0m)
return;
SellMarket(totalVolume);
_entryPrice = entryPrice;
_stopPrice = StopLossPips > 0 ? entryPrice + stopDistance : null;
_takeProfitPrice = TakeProfitPips > 0 ? entryPrice - TakeProfitPips * _pipSize : null;
}
_trailingStopPrice = TrailingStopPips > 0 ? _stopPrice : null;
}
private void UpdateDailyState(DateTimeOffset time)
{
var day = time.Date;
if (_currentDay != day)
{
_currentDay = day;
}
}
private void UpdateRangeHistory(ICandleMessage candle)
{
_highHistory.Add(candle.HighPrice);
_lowHistory.Add(candle.LowPrice);
var maxCount = Math.Max(Shift + Interval + 5, 50);
if (_highHistory.Count > maxCount)
{
var remove = _highHistory.Count - maxCount;
_highHistory.RemoveRange(0, remove);
_lowHistory.RemoveRange(0, remove);
}
}
private decimal? ProcessMovingAverage(IIndicator indicator, AppliedPrices priceMode, List<decimal> buffer, int shift, ICandleMessage candle)
{
var price = GetAppliedPrice(candle, priceMode);
var result = indicator.Process(new DecimalIndicatorValue(indicator, price, candle.OpenTime));
if (!result.IsFormed)
return null;
var value = result.GetValue<decimal>();
buffer.Add(value);
var maxSize = Math.Max(shift + 1, 100);
if (buffer.Count > maxSize)
buffer.RemoveAt(0);
var index = buffer.Count - 1 - shift;
if (index < 0 || index >= buffer.Count)
return null;
return buffer[index];
}
private bool TryGetRangeExtremes(out decimal lowest, out decimal highest)
{
lowest = 0m;
highest = 0m;
var required = Shift + Interval;
if (required <= 0)
return false;
if (_lowHistory.Count < required || _highHistory.Count < required)
return false;
var low = decimal.MaxValue;
var high = decimal.MinValue;
var total = _lowHistory.Count;
for (var offset = Shift; offset < Shift + Interval; offset++)
{
var index = total - 1 - offset;
if (index < 0)
return false;
var lowValue = _lowHistory[index];
var highValue = _highHistory[index];
if (lowValue < low)
low = lowValue;
if (highValue > high)
high = highValue;
}
if (low == decimal.MaxValue || high == decimal.MinValue)
return false;
lowest = low;
highest = high;
return true;
}
private void UpdateTrailing(ICandleMessage candle)
{
if (TrailingStopPips <= 0 || TrailingStepPips <= 0 || _entryPrice is null)
return;
var trailDistance = TrailingStopPips * _pipSize;
var stepDistance = TrailingStepPips * _pipSize;
if (Position > 0)
{
var current = candle.ClosePrice;
var entry = _entryPrice.Value;
if (current - entry > trailDistance + stepDistance)
{
var threshold = current - (trailDistance + stepDistance);
if (_stopPrice is null || _stopPrice < threshold)
{
var newStop = current - trailDistance;
if (_stopPrice is null || newStop > _stopPrice)
{
_stopPrice = newStop;
_trailingStopPrice = newStop;
}
}
}
}
else if (Position < 0)
{
var current = candle.ClosePrice;
var entry = _entryPrice.Value;
if (entry - current > trailDistance + stepDistance)
{
var threshold = current + trailDistance + stepDistance;
if (_stopPrice is null || _stopPrice > threshold)
{
var newStop = current + trailDistance;
if (_stopPrice is null || newStop < _stopPrice)
{
_stopPrice = newStop;
_trailingStopPrice = newStop;
}
}
}
}
}
private bool CheckProtectiveExits(ICandleMessage candle)
{
if (Position > 0)
{
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Position);
ResetTradeState();
return true;
}
if (_takeProfitPrice is decimal take && candle.HighPrice >= take)
{
SellMarket(Position);
ResetTradeState();
return true;
}
}
else if (Position < 0)
{
var volume = Math.Abs(Position);
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(volume);
ResetTradeState();
return true;
}
if (_takeProfitPrice is decimal take && candle.LowPrice <= take)
{
BuyMarket(volume);
ResetTradeState();
return true;
}
}
else if (_entryPrice is not null)
{
ResetTradeState();
}
return false;
}
private decimal CalculateRiskBasedVolume(decimal stopDistance)
{
if (UseManualVolume || stopDistance <= 0m)
return NormalizeVolume(Volume);
var portfolioValue = Portfolio?.CurrentValue ?? 0m;
if (portfolioValue <= 0m)
return NormalizeVolume(Volume);
var riskAmount = portfolioValue * RiskPercent / 100m;
if (riskAmount <= 0m)
return NormalizeVolume(Volume);
var step = Security?.PriceStep ?? _pipSize;
if (step <= 0m)
step = _pipSize > 0m ? _pipSize : 1m;
var stepValue = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? step;
if (stepValue <= 0m)
stepValue = step;
var steps = stopDistance / step;
if (steps <= 0m)
return NormalizeVolume(Volume);
var riskPerUnit = steps * stepValue;
if (riskPerUnit <= 0m)
return NormalizeVolume(Volume);
var rawVolume = riskAmount / riskPerUnit;
return NormalizeVolume(rawVolume);
}
private decimal NormalizeVolume(decimal volume)
{
if (Security is null)
return volume;
var step = Security.VolumeStep ?? 1m;
if (step > 0m)
volume = Math.Floor(volume / step) * step;
if (volume < step)
volume = step;
return volume;
}
private static decimal GetAppliedPrice(ICandleMessage candle, AppliedPrices mode)
{
return mode switch
{
AppliedPrices.Open => candle.OpenPrice,
AppliedPrices.High => candle.HighPrice,
AppliedPrices.Low => candle.LowPrice,
AppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPrices.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPrices.Weighted => (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m,
_ => candle.ClosePrice,
};
}
private static IIndicator CreateMovingAverage(MovingAverageMethods method, int length)
{
return method switch
{
MovingAverageMethods.Sma => new SimpleMovingAverage { Length = length },
MovingAverageMethods.Ema => new ExponentialMovingAverage { Length = length },
MovingAverageMethods.Smma => new SmoothedMovingAverage { Length = length },
MovingAverageMethods.Lwma => new WeightedMovingAverage { Length = length },
_ => new SimpleMovingAverage { Length = length },
};
}
private void ResetTradeState()
{
_entryPrice = null;
_stopPrice = null;
_takeProfitPrice = null;
_trailingStopPrice = null;
}
/// <summary>
/// Available moving average calculation methods.
/// </summary>
public enum MovingAverageMethods
{
/// <summary>
/// Simple moving average.
/// </summary>
Sma,
/// <summary>
/// Exponential moving average.
/// </summary>
Ema,
/// <summary>
/// Smoothed moving average (SMMA).
/// </summary>
Smma,
/// <summary>
/// Linear weighted moving average (LWMA).
/// </summary>
Lwma
}
/// <summary>
/// Price sources supported by the moving averages.
/// </summary>
public enum AppliedPrices
{
/// <summary>
/// Close price.
/// </summary>
Close,
/// <summary>
/// Open price.
/// </summary>
Open,
/// <summary>
/// High price.
/// </summary>
High,
/// <summary>
/// Low price.
/// </summary>
Low,
/// <summary>
/// Median price (high + low) / 2.
/// </summary>
Median,
/// <summary>
/// Typical price (high + low + close) / 3.
/// </summary>
Typical,
/// <summary>
/// Weighted close price (high + low + 2 * close) / 4.
/// </summary>
Weighted
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class sv_daily_breakout_strategy(Strategy):
def __init__(self):
super(sv_daily_breakout_strategy, self).__init__()
self._use_manual_volume = self.Param("UseManualVolume", False)
self._risk_percent = self.Param("RiskPercent", 5.0)
self._stop_loss_pips = self.Param("StopLossPips", 50)
self._take_profit_pips = self.Param("TakeProfitPips", 50)
self._trailing_stop_pips = self.Param("TrailingStopPips", 5)
self._trailing_step_pips = self.Param("TrailingStepPips", 5)
self._start_hour = self.Param("StartHour", 0)
self._start_minute = self.Param("StartMinute", 0)
self._shift = self.Param("Shift", 2)
self._interval = self.Param("Interval", 10)
self._fast_ma_period = self.Param("FastMaPeriod", 5)
self._slow_ma_period = self.Param("SlowMaPeriod", 14)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._high_history = []
self._low_history = []
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
self._trailing_stop_price = None
self._current_day = None
self._pip_size = 0.0
@property
def UseManualVolume(self):
return self._use_manual_volume.Value
@property
def RiskPercent(self):
return self._risk_percent.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def TrailingStopPips(self):
return self._trailing_stop_pips.Value
@property
def TrailingStepPips(self):
return self._trailing_step_pips.Value
@property
def StartHour(self):
return self._start_hour.Value
@property
def StartMinute(self):
return self._start_minute.Value
@property
def Shift(self):
return self._shift.Value
@property
def Interval(self):
return self._interval.Value
@property
def FastMaPeriod(self):
return self._fast_ma_period.Value
@property
def SlowMaPeriod(self):
return self._slow_ma_period.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(sv_daily_breakout_strategy, self).OnStarted2(time)
sec = self.Security
decimals = int(sec.Decimals) if sec is not None and sec.Decimals is not None else 2
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.01
factor = 10.0 if decimals == 3 or decimals == 5 else 1.0
self._pip_size = step * factor
if self._pip_size <= 0:
self._pip_size = step if step > 0 else 0.01
fast_ma = ExponentialMovingAverage()
fast_ma.Length = self.FastMaPeriod
slow_ma = ExponentialMovingAverage()
slow_ma.Length = self.SlowMaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ma, slow_ma, self._process_candle).Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
self._update_range_history(candle)
self._update_trailing(candle)
if self._check_protective_exits(candle):
return
if float(self.Position) != 0:
return
lowest, highest = self._try_get_range_extremes()
if lowest is None or highest is None:
return
if highest < slow_val and lowest < fast_val:
self._enter_position(True, candle)
return
if lowest > slow_val and highest > fast_val:
self._enter_position(False, candle)
def _enter_position(self, is_long, candle):
entry_price = float(candle.ClosePrice)
stop_distance = self.StopLossPips * self._pip_size if self.StopLossPips > 0 else 0.0
volume = float(self.Volume)
if volume <= 0:
return
pos = float(self.Position)
if is_long:
total_volume = volume + (abs(pos) if pos < 0 else 0.0)
if total_volume <= 0:
return
self.BuyMarket(total_volume)
self._entry_price = entry_price
self._stop_price = entry_price - stop_distance if self.StopLossPips > 0 else None
self._take_profit_price = entry_price + self.TakeProfitPips * self._pip_size if self.TakeProfitPips > 0 else None
else:
total_volume = volume + (pos if pos > 0 else 0.0)
if total_volume <= 0:
return
self.SellMarket(total_volume)
self._entry_price = entry_price
self._stop_price = entry_price + stop_distance if self.StopLossPips > 0 else None
self._take_profit_price = entry_price - self.TakeProfitPips * self._pip_size if self.TakeProfitPips > 0 else None
self._trailing_stop_price = self._stop_price if self.TrailingStopPips > 0 else None
def _update_range_history(self, candle):
self._high_history.append(float(candle.HighPrice))
self._low_history.append(float(candle.LowPrice))
max_count = max(self.Shift + self.Interval + 5, 50)
if len(self._high_history) > max_count:
remove = len(self._high_history) - max_count
self._high_history = self._high_history[remove:]
self._low_history = self._low_history[remove:]
def _try_get_range_extremes(self):
required = self.Shift + self.Interval
if required <= 0:
return (None, None)
if len(self._low_history) < required or len(self._high_history) < required:
return (None, None)
low = float("inf")
high = float("-inf")
total = len(self._low_history)
for offset in range(self.Shift, self.Shift + self.Interval):
index = total - 1 - offset
if index < 0:
return (None, None)
low_val = self._low_history[index]
high_val = self._high_history[index]
if low_val < low:
low = low_val
if high_val > high:
high = high_val
if low == float("inf") or high == float("-inf"):
return (None, None)
return (low, high)
def _update_trailing(self, candle):
if self.TrailingStopPips <= 0 or self.TrailingStepPips <= 0 or self._entry_price is None:
return
trail_distance = self.TrailingStopPips * self._pip_size
step_distance = self.TrailingStepPips * self._pip_size
pos = float(self.Position)
if pos > 0:
current = float(candle.ClosePrice)
entry = self._entry_price
if current - entry > trail_distance + step_distance:
threshold = current - (trail_distance + step_distance)
if self._stop_price is None or self._stop_price < threshold:
new_stop = current - trail_distance
if self._stop_price is None or new_stop > self._stop_price:
self._stop_price = new_stop
self._trailing_stop_price = new_stop
elif pos < 0:
current = float(candle.ClosePrice)
entry = self._entry_price
if entry - current > trail_distance + step_distance:
threshold = current + trail_distance + step_distance
if self._stop_price is None or self._stop_price > threshold:
new_stop = current + trail_distance
if self._stop_price is None or new_stop < self._stop_price:
self._stop_price = new_stop
self._trailing_stop_price = new_stop
def _check_protective_exits(self, candle):
pos = float(self.Position)
if pos > 0:
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(pos)
self._reset_trade_state()
return True
if self._take_profit_price is not None and float(candle.HighPrice) >= self._take_profit_price:
self.SellMarket(pos)
self._reset_trade_state()
return True
elif pos < 0:
volume = abs(pos)
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(volume)
self._reset_trade_state()
return True
if self._take_profit_price is not None and float(candle.LowPrice) <= self._take_profit_price:
self.BuyMarket(volume)
self._reset_trade_state()
return True
elif self._entry_price is not None:
self._reset_trade_state()
return False
def _reset_trade_state(self):
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
self._trailing_stop_price = None
def OnReseted(self):
super(sv_daily_breakout_strategy, self).OnReseted()
self._high_history = []
self._low_history = []
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
self._trailing_stop_price = None
self._current_day = None
self._pip_size = 0.0
def CreateClone(self):
return sv_daily_breakout_strategy()