BykovTrend ReOpen戦略
概要
BykovTrend ReOpen戦略は、Williams %RとAverage True Rangeインジケーターに基づくBykovTrendロジックを使用します。トレンドが強気に転換したときに買いシグナルが発生し、弱気に転換したときに売りシグナルが発生します。ポジションに入った後、戦略はトレンドが続く間、事前に定義された価格ステップごとに追加ポジションを再度開くことができます。ストップロスとテイクプロフィットは最後のエントリー価格から適用されます。
インジケーター
この戦略は別のインジケーターファイルを必要としません。以下を使用してシグナルを計算します:
- Williams %R 期間
SSP。 - ATR 固定期間15。
Williams %Rが閾値
-100 + Kと-K(K = 33 - Risk)をクロスしたときにトレンドが切り替わります。
取引ルール
- 強気シグナルで、ショートポジションを閉じ(許可されている場合)、ロングポジションを開きます。
- 弱気シグナルで、ロングポジションを閉じ(許可されている場合)、ショートポジションを開きます。
- ポジションが開いている間、
Max Positionsに達するまでPrice Step単位ごとに同方向への新規ポジションが追加されます。 - 各ポジションには最後のエントリー価格から計測されたストップロスとテイクプロフィット距離があります。
パラメーター
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()