BykovTrend ReOpen Strategy
Overview
The BykovTrend ReOpen strategy uses the BykovTrend logic based on Williams %R and Average True Range indicators. A buy signal occurs when the trend turns bullish and a sell signal when it turns bearish. After entering a position the strategy can re-open additional positions every predefined price step while the trend continues. Stop-loss and take-profit are applied from the last entry price.
Indicator
The strategy does not require a separate indicator file. It calculates signals using:
- Williams %R with period
SSP. - ATR with fixed period 15.
Trend switches when Williams %R crosses the thresholds
-100 + Kand-K, whereK = 33 - Risk.
Trading rules
- On a bullish signal close short positions (if allowed) and open a long position.
- On a bearish signal close long positions (if allowed) and open a short position.
- While a position is open, new positions in the same direction are added every
Price Stepunits untilMax Positionsis reached. - Each position has stop-loss and take-profit distances measured from the last entry price.
Parameters
Risk– risk factor defining indicator thresholds.SSP– Williams %R period.Price Step– price distance to add a new position.Max Positions– maximum number of open positions per side.Stop Loss– stop-loss distance in price units.Take Profit– take-profit distance in price units.Enable Long Open– allow opening long positions.Enable Short Open– allow opening short positions.Enable Long Close– allow closing longs on opposite signal.Enable Short Close– allow closing shorts on opposite signal.Candle Type– timeframe used for calculations.
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>
/// BykovTrend strategy with position re-opening.
/// Uses Williams %R and ATR to detect trend changes.
/// Re-opens positions every fixed price step while trend persists.
/// </summary>
public class BykovTrendReOpenStrategy : Strategy
{
private readonly StrategyParam<int> _risk;
private readonly StrategyParam<int> _ssp;
private readonly StrategyParam<decimal> _priceStep;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<bool> _enableLongOpen;
private readonly StrategyParam<bool> _enableShortOpen;
private readonly StrategyParam<bool> _enableLongClose;
private readonly StrategyParam<bool> _enableShortClose;
private readonly StrategyParam<DataType> _candleType;
private decimal _lastBuyPrice;
private decimal _lastSellPrice;
private int _buyCount;
private int _sellCount;
private bool _trendUp;
/// <summary>
/// Risk parameter for trend detection.
/// </summary>
public int Risk
{
get => _risk.Value;
set => _risk.Value = value;
}
/// <summary>
/// Williams %R period.
/// </summary>
public int Ssp
{
get => _ssp.Value;
set => _ssp.Value = value;
}
/// <summary>
/// Price distance to re-open position.
/// </summary>
public decimal PriceStep
{
get => _priceStep.Value;
set => _priceStep.Value = value;
}
/// <summary>
/// Maximum number of positions in one direction.
/// </summary>
public int MaxPositions
{
get => _maxPositions.Value;
set => _maxPositions.Value = value;
}
/// <summary>
/// Stop-loss distance in price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take-profit distance in price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool EnableLongOpen
{
get => _enableLongOpen.Value;
set => _enableLongOpen.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool EnableShortOpen
{
get => _enableShortOpen.Value;
set => _enableShortOpen.Value = value;
}
/// <summary>
/// Allow closing long positions on opposite signals.
/// </summary>
public bool EnableLongClose
{
get => _enableLongClose.Value;
set => _enableLongClose.Value = value;
}
/// <summary>
/// Allow closing short positions on opposite signals.
/// </summary>
public bool EnableShortClose
{
get => _enableShortClose.Value;
set => _enableShortClose.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BykovTrendReOpenStrategy()
{
_risk = Param(nameof(Risk), 3)
.SetGreaterThanZero()
.SetDisplay("Risk", "Risk parameter for BykovTrend", "Indicator")
.SetOptimize(1, 10, 1);
_ssp = Param(nameof(Ssp), 9)
.SetGreaterThanZero()
.SetDisplay("SSP", "Williams %R period", "Indicator")
.SetOptimize(5, 20, 1);
_priceStep = Param(nameof(PriceStep), 300m)
.SetGreaterThanZero()
.SetDisplay("Price Step", "Distance for re-open", "Trading")
.SetOptimize(100m, 1000m, 100m);
_maxPositions = Param(nameof(MaxPositions), 10)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum positions per side", "Trading")
.SetOptimize(1, 20, 1);
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop-loss distance", "Risk")
.SetOptimize(100m, 2000m, 100m);
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take-profit distance", "Risk")
.SetOptimize(100m, 4000m, 100m);
_enableLongOpen = Param(nameof(EnableLongOpen), true)
.SetDisplay("Enable Long Open", "Allow long entries", "Trading");
_enableShortOpen = Param(nameof(EnableShortOpen), true)
.SetDisplay("Enable Short Open", "Allow short entries", "Trading");
_enableLongClose = Param(nameof(EnableLongClose), true)
.SetDisplay("Enable Long Close", "Allow long exits", "Trading");
_enableShortClose = Param(nameof(EnableShortClose), true)
.SetDisplay("Enable Short Close", "Allow short exits", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for indicator", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastBuyPrice = 0m;
_lastSellPrice = 0m;
_buyCount = 0;
_sellCount = 0;
_trendUp = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var wpr = new WilliamsR { Length = Ssp };
var atr = new AverageTrueRange { Length = 15 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(wpr, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, wpr);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal wpr, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var k = 33 - Risk;
var newTrend = _trendUp;
if (wpr < -100 + k)
newTrend = false;
if (wpr > -k)
newTrend = true;
var buySignal = !_trendUp && newTrend;
var sellSignal = _trendUp && !newTrend;
if (buySignal)
{
if (EnableShortClose)
CloseShortPositions();
TryOpenLong(candle.ClosePrice);
}
else if (sellSignal)
{
if (EnableLongClose)
CloseLongPositions();
TryOpenShort(candle.ClosePrice);
}
if (Position > 0)
CheckRebuy(candle.ClosePrice);
else if (Position < 0)
CheckResell(candle.ClosePrice);
_trendUp = newTrend;
}
private void TryOpenLong(decimal price)
{
if (!EnableLongOpen || Position > 0)
return;
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_lastBuyPrice = price;
_buyCount = 1;
LogInfo($"Open long at {price}");
}
private void TryOpenShort(decimal price)
{
if (!EnableShortOpen || Position < 0)
return;
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_lastSellPrice = price;
_sellCount = 1;
LogInfo($"Open short at {price}");
}
private void CloseLongPositions()
{
if (Position <= 0)
return;
SellMarket(Math.Abs(Position));
ResetState();
LogInfo("Close long positions");
}
private void CloseShortPositions()
{
if (Position >= 0)
return;
BuyMarket(Math.Abs(Position));
ResetState();
LogInfo("Close short positions");
}
private void CheckRebuy(decimal price)
{
if (_buyCount >= MaxPositions)
{
CheckStops(price, true);
return;
}
if (price - _lastBuyPrice >= PriceStep)
{
BuyMarket(Volume);
_lastBuyPrice = price;
_buyCount++;
LogInfo($"Re-open long at {price}");
}
CheckStops(price, true);
}
private void CheckResell(decimal price)
{
if (_sellCount >= MaxPositions)
{
CheckStops(price, false);
return;
}
if (_lastSellPrice - price >= PriceStep)
{
SellMarket(Volume);
_lastSellPrice = price;
_sellCount++;
LogInfo($"Re-open short at {price}");
}
CheckStops(price, false);
}
private void CheckStops(decimal price, bool isLong)
{
var entry = isLong ? _lastBuyPrice : _lastSellPrice;
if (StopLoss > 0)
{
var stopPrice = isLong ? entry - StopLoss : entry + StopLoss;
if (isLong ? price <= stopPrice : price >= stopPrice)
{
if (isLong)
SellMarket(Math.Abs(Position));
else
BuyMarket(Math.Abs(Position));
ResetState();
LogInfo($"Stop loss triggered at {price}");
return;
}
}
if (TakeProfit > 0)
{
var target = isLong ? entry + TakeProfit : entry - TakeProfit;
if (isLong ? price >= target : price <= target)
{
if (isLong)
SellMarket(Math.Abs(Position));
else
BuyMarket(Math.Abs(Position));
ResetState();
LogInfo($"Take profit triggered at {price}");
}
}
}
private void ResetState()
{
_lastBuyPrice = 0m;
_lastSellPrice = 0m;
_buyCount = 0;
_sellCount = 0;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import WilliamsR, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class bykov_trend_re_open_strategy(Strategy):
def __init__(self):
super(bykov_trend_re_open_strategy, self).__init__()
self._risk = self.Param("Risk", 3)
self._ssp = self.Param("Ssp", 9)
self._price_step_param = self.Param("PriceStep", 300.0)
self._max_positions = self.Param("MaxPositions", 10)
self._stop_loss = self.Param("StopLoss", 1000.0)
self._take_profit = self.Param("TakeProfit", 2000.0)
self._enable_long_open = self.Param("EnableLongOpen", True)
self._enable_short_open = self.Param("EnableShortOpen", True)
self._enable_long_close = self.Param("EnableLongClose", True)
self._enable_short_close = self.Param("EnableShortClose", True)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._last_buy_price = 0.0
self._last_sell_price = 0.0
self._buy_count = 0
self._sell_count = 0
self._trend_up = False
@property
def Risk(self):
return self._risk.Value
@Risk.setter
def Risk(self, value):
self._risk.Value = value
@property
def Ssp(self):
return self._ssp.Value
@Ssp.setter
def Ssp(self, value):
self._ssp.Value = value
@property
def PriceStepParam(self):
return self._price_step_param.Value
@PriceStepParam.setter
def PriceStepParam(self, value):
self._price_step_param.Value = value
@property
def MaxPositions(self):
return self._max_positions.Value
@MaxPositions.setter
def MaxPositions(self, value):
self._max_positions.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def EnableLongOpen(self):
return self._enable_long_open.Value
@EnableLongOpen.setter
def EnableLongOpen(self, value):
self._enable_long_open.Value = value
@property
def EnableShortOpen(self):
return self._enable_short_open.Value
@EnableShortOpen.setter
def EnableShortOpen(self, value):
self._enable_short_open.Value = value
@property
def EnableLongClose(self):
return self._enable_long_close.Value
@EnableLongClose.setter
def EnableLongClose(self, value):
self._enable_long_close.Value = value
@property
def EnableShortClose(self):
return self._enable_short_close.Value
@EnableShortClose.setter
def EnableShortClose(self, value):
self._enable_short_close.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(bykov_trend_re_open_strategy, self).OnStarted2(time)
self._last_buy_price = 0.0
self._last_sell_price = 0.0
self._buy_count = 0
self._sell_count = 0
self._trend_up = False
wpr = WilliamsR()
wpr.Length = self.Ssp
atr = AverageTrueRange()
atr.Length = 15
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(wpr, atr, self.ProcessCandle).Start()
self.StartProtection(
Unit(2000.0, UnitTypes.Absolute),
Unit(1000.0, UnitTypes.Absolute))
def ProcessCandle(self, candle, wpr_value, atr_value):
if candle.State != CandleStates.Finished:
return
wpr = float(wpr_value)
close = float(candle.ClosePrice)
k = 33 - int(self.Risk)
new_trend = self._trend_up
if wpr < -100.0 + k:
new_trend = False
if wpr > -k:
new_trend = True
buy_signal = not self._trend_up and new_trend
sell_signal = self._trend_up and not new_trend
price_step = float(self.PriceStepParam)
sl = float(self.StopLoss)
tp = float(self.TakeProfit)
max_pos = int(self.MaxPositions)
if buy_signal:
if self.EnableShortClose and self.Position < 0:
self.BuyMarket()
self._reset_state()
if self.EnableLongOpen and self.Position <= 0:
self.BuyMarket()
self._last_buy_price = close
self._buy_count = 1
elif sell_signal:
if self.EnableLongClose and self.Position > 0:
self.SellMarket()
self._reset_state()
if self.EnableShortOpen and self.Position >= 0:
self.SellMarket()
self._last_sell_price = close
self._sell_count = 1
if self.Position > 0:
self._check_rebuy(close, price_step, max_pos, sl, tp)
elif self.Position < 0:
self._check_resell(close, price_step, max_pos, sl, tp)
self._trend_up = new_trend
def _check_rebuy(self, price, price_step, max_pos, sl, tp):
if self._buy_count < max_pos and price - self._last_buy_price >= price_step:
self.BuyMarket()
self._last_buy_price = price
self._buy_count += 1
self._check_stops(price, True, sl, tp)
def _check_resell(self, price, price_step, max_pos, sl, tp):
if self._sell_count < max_pos and self._last_sell_price - price >= price_step:
self.SellMarket()
self._last_sell_price = price
self._sell_count += 1
self._check_stops(price, False, sl, tp)
def _check_stops(self, price, is_long, sl, tp):
entry = self._last_buy_price if is_long else self._last_sell_price
if sl > 0.0:
stop_price = entry - sl if is_long else entry + sl
if (is_long and price <= stop_price) or (not is_long and price >= stop_price):
if is_long:
self.SellMarket()
else:
self.BuyMarket()
self._reset_state()
return
if tp > 0.0:
target = entry + tp if is_long else entry - tp
if (is_long and price >= target) or (not is_long and price <= target):
if is_long:
self.SellMarket()
else:
self.BuyMarket()
self._reset_state()
def _reset_state(self):
self._last_buy_price = 0.0
self._last_sell_price = 0.0
self._buy_count = 0
self._sell_count = 0
def OnReseted(self):
super(bykov_trend_re_open_strategy, self).OnReseted()
self._last_buy_price = 0.0
self._last_sell_price = 0.0
self._buy_count = 0
self._sell_count = 0
self._trend_up = False
def CreateClone(self):
return bykov_trend_re_open_strategy()