JBrainTrend ReOpen 策略
该策略基于 MQL5 示例 “JBrainTrend1Stop_ReOpen” 的思想,并在 C# 中实现。
它使用随机指标判断超买和超卖区域,并在价格按指定步长继续向持仓方向移动时加仓。
逻辑
- 订阅选定时间框架的 K 线。
- 计算随机指标 (%K 和 %D)。
- 当 %K 低于 20 时开多仓,当 %K 高于 80 时开空仓。
- 当相反极值出现时平仓。
- 开仓后,若价格按照
PriceStep继续移动,则在同方向追加仓位,直到达到MaxPositions。 - 使用绝对价格单位的止损和止盈保护头寸。
参数
StochPeriod– 随机指标的主周期。KPeriod/DPeriod– %K 和 %D 的平滑周期。CandleType– 用于分析的时间框架。StopLoss– 以价格单位表示的止损距离。TakeProfit– 以价格单位表示的止盈距离。PriceStep– 重新加仓所需的价格移动。MaxPositions– 同方向的最大加仓次数。BuyEnabled/SellEnabled– 是否允许做多或做空。
说明
原始 MQL5 脚本使用名为 JBrainTrend1Stop 的自定义指标。
此 C# 版本使用 StockSharp 内置指标实现类似的交易理念,以便于集成。
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>
/// Strategy based on the Stochastic oscillator with position re-opening capability.
/// Opens a position when the market enters oversold/overbought zones and
/// re-enters in the same direction after price moves by a defined step.
/// </summary>
public class JBrainTrendReopenStrategy : Strategy
{
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<int> _kSmoothing;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _priceStep;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<bool> _buyEnabled;
private readonly StrategyParam<bool> _sellEnabled;
private decimal _lastEntryPrice;
private int _entriesCount;
private bool _isLong;
public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.Value = value; }
public int KSmoothing { get => _kSmoothing.Value; set => _kSmoothing.Value = value; }
public int DPeriod { get => _dPeriod.Value; set => _dPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal PriceStep { get => _priceStep.Value; set => _priceStep.Value = value; }
public int MaxPositions { get => _maxPositions.Value; set => _maxPositions.Value = value; }
public bool BuyEnabled { get => _buyEnabled.Value; set => _buyEnabled.Value = value; }
public bool SellEnabled { get => _sellEnabled.Value; set => _sellEnabled.Value = value; }
public JBrainTrendReopenStrategy()
{
_stochPeriod = Param(nameof(StochPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Main period for Stochastic oscillator", "Indicators");
_kSmoothing = Param(nameof(KSmoothing), 3)
.SetGreaterThanZero()
.SetDisplay("K Smoothing", "Smoothing for %K line", "Indicators");
_dPeriod = Param(nameof(DPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("D Period", "Smoothing for %D line", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Timeframe", "Timeframe for calculations", "General");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit in price units", "Risk");
_priceStep = Param(nameof(PriceStep), 300m)
.SetGreaterThanZero()
.SetDisplay("Re-entry Step", "Price move to add position", "Risk");
_maxPositions = Param(nameof(MaxPositions), 1)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum entries in one direction", "Risk");
_buyEnabled = Param(nameof(BuyEnabled), true)
.SetDisplay("Allow Long", "Enable long trades", "General");
_sellEnabled = Param(nameof(SellEnabled), true)
.SetDisplay("Allow Short", "Enable short trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastEntryPrice = 0m;
_entriesCount = 0;
_isLong = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = StochPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ProcessCandle)
.Start();
StartProtection(
new Unit(TakeProfit, UnitTypes.Absolute),
new Unit(StopLoss, UnitTypes.Absolute));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
if (Position == 0)
_entriesCount = 0;
// Buy signal: oversold
if (rsiValue < 30m && Position <= 0 && BuyEnabled)
{
BuyMarket();
_isLong = true;
_lastEntryPrice = price;
_entriesCount = 1;
return;
}
// Sell signal: overbought
if (rsiValue > 70m && Position >= 0 && SellEnabled)
{
SellMarket();
_isLong = false;
_lastEntryPrice = price;
_entriesCount = 1;
return;
}
// Exit long on overbought
if (Position > 0 && rsiValue > 70m)
{
SellMarket();
return;
}
// Exit short on oversold
if (Position < 0 && rsiValue < 30m)
{
BuyMarket();
return;
}
// Re-entry logic
if (_entriesCount > 0 && _entriesCount < MaxPositions)
{
if (_isLong && Position > 0 && price - _lastEntryPrice >= PriceStep)
{
BuyMarket();
_lastEntryPrice = price;
_entriesCount++;
}
else if (!_isLong && Position < 0 && _lastEntryPrice - price >= PriceStep)
{
SellMarket();
_lastEntryPrice = price;
_entriesCount++;
}
}
}
}
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 RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class j_brain_trend_reopen_strategy(Strategy):
def __init__(self):
super(j_brain_trend_reopen_strategy, self).__init__()
self._stoch_period = self.Param("StochPeriod", 9)
self._k_smoothing = self.Param("KSmoothing", 3)
self._d_period = self.Param("DPeriod", 3)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._stop_loss = self.Param("StopLoss", 1000.0)
self._take_profit = self.Param("TakeProfit", 2000.0)
self._price_step = self.Param("PriceStep", 300.0)
self._max_positions = self.Param("MaxPositions", 1)
self._buy_enabled = self.Param("BuyEnabled", True)
self._sell_enabled = self.Param("SellEnabled", True)
self._last_entry_price = 0.0
self._entries_count = 0
self._is_long = False
@property
def StochPeriod(self):
return self._stoch_period.Value
@StochPeriod.setter
def StochPeriod(self, value):
self._stoch_period.Value = value
@property
def KSmoothing(self):
return self._k_smoothing.Value
@KSmoothing.setter
def KSmoothing(self, value):
self._k_smoothing.Value = value
@property
def DPeriod(self):
return self._d_period.Value
@DPeriod.setter
def DPeriod(self, value):
self._d_period.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.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 PriceStep(self):
return self._price_step.Value
@PriceStep.setter
def PriceStep(self, value):
self._price_step.Value = value
@property
def MaxPositions(self):
return self._max_positions.Value
@MaxPositions.setter
def MaxPositions(self, value):
self._max_positions.Value = value
@property
def BuyEnabled(self):
return self._buy_enabled.Value
@BuyEnabled.setter
def BuyEnabled(self, value):
self._buy_enabled.Value = value
@property
def SellEnabled(self):
return self._sell_enabled.Value
@SellEnabled.setter
def SellEnabled(self, value):
self._sell_enabled.Value = value
def OnStarted2(self, time):
super(j_brain_trend_reopen_strategy, self).OnStarted2(time)
self._last_entry_price = 0.0
self._entries_count = 0
self._is_long = False
rsi = RelativeStrengthIndex()
rsi.Length = self.StochPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self.ProcessCandle).Start()
self.StartProtection(
Unit(self.TakeProfit, UnitTypes.Absolute),
Unit(self.StopLoss, UnitTypes.Absolute))
def ProcessCandle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi_value)
price = float(candle.ClosePrice)
if self.Position == 0:
self._entries_count = 0
if rsi_val < 30.0 and self.Position <= 0 and self.BuyEnabled:
self.BuyMarket()
self._is_long = True
self._last_entry_price = price
self._entries_count = 1
return
if rsi_val > 70.0 and self.Position >= 0 and self.SellEnabled:
self.SellMarket()
self._is_long = False
self._last_entry_price = price
self._entries_count = 1
return
if self.Position > 0 and rsi_val > 70.0:
self.SellMarket()
return
if self.Position < 0 and rsi_val < 30.0:
self.BuyMarket()
return
if self._entries_count > 0 and self._entries_count < int(self.MaxPositions):
if self._is_long and self.Position > 0 and price - self._last_entry_price >= float(self.PriceStep):
self.BuyMarket()
self._last_entry_price = price
self._entries_count += 1
elif not self._is_long and self.Position < 0 and self._last_entry_price - price >= float(self.PriceStep):
self.SellMarket()
self._last_entry_price = price
self._entries_count += 1
def OnReseted(self):
super(j_brain_trend_reopen_strategy, self).OnReseted()
self._last_entry_price = 0.0
self._entries_count = 0
self._is_long = False
def CreateClone(self):
return j_brain_trend_reopen_strategy()