Estrategia BykovTrend ReOpen
Descripción general
La estrategia BykovTrend ReOpen utiliza la lógica de BykovTrend basada en los indicadores Williams %R y Average True Range. Una señal de compra ocurre cuando la tendencia se vuelve alcista y una señal de venta cuando se vuelve bajista. Después de entrar en una posición, la estrategia puede reabrir posiciones adicionales en cada paso de precio predefinido mientras la tendencia continúa. El stop loss y el take profit se aplican desde el precio del último entrada.
Indicador
La estrategia no requiere un archivo de indicador separado. Calcula las señales utilizando:
- Williams %R con período
SSP. - ATR con período fijo de 15.
La tendencia cambia cuando Williams %R cruza los umbrales
-100 + Ky-K, dondeK = 33 - Risk.
Reglas de operación
- En una señal alcista, cierra las posiciones cortas (si está permitido) y abre una posición larga.
- En una señal bajista, cierra las posiciones largas (si está permitido) y abre una posición corta.
- Mientras una posición está abierta, se añaden nuevas posiciones en la misma dirección cada
Price Stepunidades hasta alcanzarMax Positions. - Cada posición tiene distancias de stop loss y take profit medidas desde el último precio de entrada.
Parámetros
Risk– factor de riesgo que define los umbrales del indicador.SSP– período de Williams %R.Price Step– distancia de precio para añadir una nueva posición.Max Positions– número máximo de posiciones abiertas por lado.Stop Loss– distancia de stop loss en unidades de precio.Take Profit– distancia de take profit en unidades de precio.Enable Long Open– permitir la apertura de posiciones largas.Enable Short Open– permitir la apertura de posiciones cortas.Enable Long Close– permitir el cierre de posiciones largas en señal contraria.Enable Short Close– permitir el cierre de posiciones cortas en señal contraria.Candle Type– marco temporal utilizado para los cálculos.
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()