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>
/// Breakout strategy that trades when the closing price crosses a predefined level.
/// The strategy sizes positions based on the selected risk percentage and applies static stop-loss and take-profit levels.
/// </summary>
public class BrakeoutTraderV1Strategy : Strategy
{
private readonly StrategyParam<decimal> _breakoutLevel;
private readonly StrategyParam<bool> _enableLong;
private readonly StrategyParam<bool> _enableShort;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousClose;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _pipSize;
/// <summary>
/// Price level that must be broken to generate signals.
/// </summary>
public decimal BreakoutLevel
{
get => _breakoutLevel.Value;
set => _breakoutLevel.Value = value;
}
/// <summary>
/// Enables or disables long breakout trades.
/// </summary>
public bool EnableLong
{
get => _enableLong.Value;
set => _enableLong.Value = value;
}
/// <summary>
/// Enables or disables short breakout trades.
/// </summary>
public bool EnableShort
{
get => _enableShort.Value;
set => _enableShort.Value = value;
}
/// <summary>
/// Stop-loss distance in points relative to the pip size.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in points relative to the pip size.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Percentage of account equity to risk on each trade.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Candle type used to evaluate breakouts.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="BrakeoutTraderV1Strategy"/>.
/// </summary>
public BrakeoutTraderV1Strategy()
{
_breakoutLevel = Param(nameof(BreakoutLevel), 65000m)
.SetDisplay("Breakout Level", "Static price level monitored for breakouts", "Signal");
_enableLong = Param(nameof(EnableLong), true)
.SetDisplay("Enable Long", "Allow long breakout positions", "Signal");
_enableShort = Param(nameof(EnableShort), true)
.SetDisplay("Enable Short", "Allow short breakout positions", "Signal");
_stopLossPoints = Param(nameof(StopLossPoints), 140m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss Points", "Stop-loss distance expressed in pip points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 180m)
.SetGreaterThanZero()
.SetDisplay("Take Profit Points", "Take-profit distance expressed in pip points", "Risk");
_riskPercent = Param(nameof(RiskPercent), 10m)
.SetGreaterThanZero()
.SetDisplay("Risk %", "Percentage of equity risked per trade", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Time frame used for breakout detection", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return new[] { (Security, CandleType) };
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousClose = null;
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
_pipSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var priceStep = Security?.PriceStep ?? 1m;
var decimals = Security?.Decimals;
_pipSize = priceStep;
if (decimals is 3 or 5)
_pipSize *= 10m;
if (_pipSize <= 0m)
_pipSize = 1m;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
// removed StartProtection(null, null)
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (ManageOpenPosition(candle))
{
UpdatePreviousClose(candle.ClosePrice);
return;
}
var prevClose = _previousClose;
if (prevClose is null)
{
_previousClose = candle.ClosePrice;
return;
}
var currentClose = candle.ClosePrice;
var prevValue = prevClose.Value;
var breakoutUp = currentClose > BreakoutLevel && prevValue <= BreakoutLevel;
var breakoutDown = currentClose < BreakoutLevel && prevValue >= BreakoutLevel;
if (breakoutUp && EnableLong)
{
EnterLong(currentClose);
}
else if (breakoutDown && EnableShort)
{
EnterShort(currentClose);
}
_previousClose = currentClose;
}
private bool ManageOpenPosition(ICandleMessage candle)
{
if (Position > 0)
{
// Exit long if stop-loss is touched.
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket();
ResetPositionState();
return true;
}
// Exit long if take-profit is reached.
if (_takePrice is decimal take && candle.HighPrice >= take)
{
SellMarket();
ResetPositionState();
return true;
}
}
else if (Position < 0)
{
// Exit short if stop-loss is touched.
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket();
ResetPositionState();
return true;
}
// Exit short if take-profit is reached.
if (_takePrice is decimal take && candle.LowPrice <= take)
{
BuyMarket();
ResetPositionState();
return true;
}
}
else if (_entryPrice.HasValue)
{
// Reset cached levels after position is fully closed.
ResetPositionState();
}
return false;
}
private void EnterLong(decimal price)
{
if (Position > 0)
return;
var volume = CalculateOrderVolume();
var closingVolume = Position < 0 ? Math.Abs(Position) : 0m;
var totalVolume = closingVolume + volume;
if (totalVolume <= 0m)
return;
if (closingVolume > 0m)
ResetPositionState();
BuyMarket();
SetPositionTargets(price, true, volume > 0m);
}
private void EnterShort(decimal price)
{
if (Position < 0)
return;
var volume = CalculateOrderVolume();
var closingVolume = Position > 0 ? Position : 0m;
var totalVolume = closingVolume + volume;
if (totalVolume <= 0m)
return;
if (closingVolume > 0m)
ResetPositionState();
SellMarket();
SetPositionTargets(price, false, volume > 0m);
}
private void SetPositionTargets(decimal entryPrice, bool isLong, bool hasNewPosition)
{
if (!hasNewPosition)
{
return;
}
_entryPrice = entryPrice;
if (StopLossPoints > 0m && _pipSize > 0m)
_stopPrice = isLong
? entryPrice - StopLossPoints * _pipSize
: entryPrice + StopLossPoints * _pipSize;
else
_stopPrice = null;
if (TakeProfitPoints > 0m && _pipSize > 0m)
_takePrice = isLong
? entryPrice + TakeProfitPoints * _pipSize
: entryPrice - TakeProfitPoints * _pipSize;
else
_takePrice = null;
}
private decimal CalculateOrderVolume()
{
var baseVolume = Volume;
var stopDistance = StopLossPoints * _pipSize;
if (stopDistance <= 0m || RiskPercent <= 0m)
return AdjustVolume(baseVolume);
var equity = Portfolio?.CurrentValue ?? 0m;
if (equity <= 0m)
return AdjustVolume(baseVolume);
var riskValue = equity * RiskPercent / 100m;
if (riskValue <= 0m)
return AdjustVolume(baseVolume);
var qty = riskValue / stopDistance;
var adjusted = AdjustVolume(qty);
return adjusted > 0m ? adjusted : AdjustVolume(baseVolume);
}
private decimal AdjustVolume(decimal volume)
{
var security = Security;
if (security != null)
{
var step = security.VolumeStep;
if (step is decimal s && s > 0m)
{
volume = Math.Floor(volume / s) * s;
}
var min = security.MinVolume;
if (min is decimal minVol && volume < minVol)
volume = minVol;
var max = security.MaxVolume;
if (max is decimal maxVol && maxVol > 0m && volume > maxVol)
volume = maxVol;
if (volume <= 0m)
volume = step is decimal stepVal && stepVal > 0m ? stepVal : 0m;
}
if (volume <= 0m)
volume = volume == 0m ? 1m : Math.Abs(volume);
return volume;
}
private void UpdatePreviousClose(decimal close)
{
_previousClose = close;
}
private void ResetPositionState()
{
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class brakeout_trader_v1_strategy(Strategy):
def __init__(self):
super(brakeout_trader_v1_strategy, self).__init__()
self._breakout_level = self.Param("BreakoutLevel", 65000.0)
self._enable_long = self.Param("EnableLong", True)
self._enable_short = self.Param("EnableShort", True)
self._stop_loss_points = self.Param("StopLossPoints", 140.0)
self._take_profit_points = self.Param("TakeProfitPoints", 180.0)
self._risk_percent = self.Param("RiskPercent", 10.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._previous_close = None
self._entry_price = None
self._stop_price = None
self._take_price = None
self._pip_size = 0.0
@property
def BreakoutLevel(self):
return self._breakout_level.Value
@BreakoutLevel.setter
def BreakoutLevel(self, value):
self._breakout_level.Value = value
@property
def EnableLong(self):
return self._enable_long.Value
@EnableLong.setter
def EnableLong(self, value):
self._enable_long.Value = value
@property
def EnableShort(self):
return self._enable_short.Value
@EnableShort.setter
def EnableShort(self, value):
self._enable_short.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def RiskPercent(self):
return self._risk_percent.Value
@RiskPercent.setter
def RiskPercent(self, value):
self._risk_percent.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(brakeout_trader_v1_strategy, self).OnStarted2(time)
ps = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
self._pip_size = ps
if self.Security is not None and self.Security.Decimals is not None:
d = self.Security.Decimals
if d == 3 or d == 5:
self._pip_size = ps * 10.0
if self._pip_size <= 0.0:
self._pip_size = 1.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
Unit(2000.0, UnitTypes.Absolute),
Unit(1000.0, UnitTypes.Absolute))
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._manage_open_position(candle):
self._previous_close = float(candle.ClosePrice)
return
if self._previous_close is None:
self._previous_close = float(candle.ClosePrice)
return
current_close = float(candle.ClosePrice)
prev_value = self._previous_close
level = float(self.BreakoutLevel)
breakout_up = current_close > level and prev_value <= level
breakout_down = current_close < level and prev_value >= level
if breakout_up and self.EnableLong:
self._enter_long(current_close)
elif breakout_down and self.EnableShort:
self._enter_short(current_close)
self._previous_close = current_close
def _manage_open_position(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if self._stop_price is not None and low <= self._stop_price:
self.SellMarket()
self._reset_position_state()
return True
if self._take_price is not None and high >= self._take_price:
self.SellMarket()
self._reset_position_state()
return True
elif self.Position < 0:
if self._stop_price is not None and high >= self._stop_price:
self.BuyMarket()
self._reset_position_state()
return True
if self._take_price is not None and low <= self._take_price:
self.BuyMarket()
self._reset_position_state()
return True
elif self._entry_price is not None:
self._reset_position_state()
return False
def _enter_long(self, price):
if self.Position > 0:
return
if self.Position < 0:
self._reset_position_state()
self.BuyMarket()
self._set_position_targets(price, True)
def _enter_short(self, price):
if self.Position < 0:
return
if self.Position > 0:
self._reset_position_state()
self.SellMarket()
self._set_position_targets(price, False)
def _set_position_targets(self, entry_price, is_long):
self._entry_price = entry_price
sl = float(self.StopLossPoints)
tp = float(self.TakeProfitPoints)
if sl > 0.0 and self._pip_size > 0.0:
self._stop_price = entry_price - sl * self._pip_size if is_long else entry_price + sl * self._pip_size
else:
self._stop_price = None
if tp > 0.0 and self._pip_size > 0.0:
self._take_price = entry_price + tp * self._pip_size if is_long else entry_price - tp * self._pip_size
else:
self._take_price = None
def _reset_position_state(self):
self._entry_price = None
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(brakeout_trader_v1_strategy, self).OnReseted()
self._previous_close = None
self._entry_price = None
self._stop_price = None
self._take_price = None
self._pip_size = 0.0
def CreateClone(self):
return brakeout_trader_v1_strategy()