BykovTrend ReOpen 策略
概述
BykovTrend ReOpen 策略基于 Williams %R 和平均真实波幅(ATR)来判定趋势。当趋势转为上升时产生买入信号,趋势转为下降时产生卖出信号。进入仓位后,只要趋势持续,策略会在每个设定的价格步长重新加仓。止损和止盈以最近一次开仓价为基准。
指标
策略直接使用以下指标计算信号:
- Williams %R,周期为
SSP。 - ATR,固定周期 15。
当 Williams %R 穿越阈值
-100 + K和-K(其中K = 33 - Risk)时认为趋势改变。
交易规则
- 出现看涨信号时关闭空头仓位(若允许)并开多头。
- 出现看跌信号时关闭多头仓位(若允许)并开空头。
- 持有仓位期间,只要价格每移动
Price Step单位且未达到Max Positions,便在同方向加仓。 - 每次开仓都设置距离为
Stop Loss和Take Profit的止损和止盈。
参数
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()