Стратегия BykovTrend ReOpen
Обзор
Стратегия BykovTrend ReOpen использует логику индикатора BykovTrend на основе Williams %R и Average True Range. Сигнал на покупку возникает при смене тренда на восходящий, сигнал на продажу — при смене на нисходящий. После входа в позицию стратегия может повторно открывать дополнительные позиции через заданный шаг цены при сохранении тренда. Стоп‑лосс и тейк‑профит рассчитываются от цены последнего входа.
Индикатор
Отдельный файл индикатора не требуется. Сигналы вычисляются при помощи:
- Williams %R с периодом
SSP. - ATR с фиксированным периодом 15.
Смена тренда происходит при пересечении уровня
-100 + Kи-K, гдеK = 33 - Risk.
Правила торговли
- При бычьем сигнале закрываются короткие позиции (если разрешено) и открывается длинная.
- При медвежьем сигнале закрываются длинные позиции (если разрешено) и открывается короткая.
- Пока позиция открыта, новые позиции в том же направлении добавляются каждые
Price Stepпунктов до достиженияMax Positions. - Для каждой позиции задаются уровни стоп‑лосса и тейк‑профита от последней цены входа.
Параметры
Risk– параметр риска, определяющий пороги индикатора.SSP– период Williams %R.Price Step– дистанция по цене для добавления позиции.Max Positions– максимальное количество открытых позиций в одном направлении.Stop Loss– расстояние стоп‑лосса в ценовых единицах.Take Profit– расстояние тейк‑профита в ценовых единицах.Enable Long Open– разрешить открытие длинных позиций.Enable Short Open– разрешить открытие коротких позиций.Enable Long Close– разрешить закрытие длинных при обратном сигнале.Enable Short Close– разрешить закрытие коротких при обратном сигнале.Candle Type– таймфрейм для вычислений.
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()