Bezier ReOpen Strategy
The Bezier ReOpen Strategy applies a custom Bezier curve indicator to follow trend direction. When the indicator turns upward and the latest value is above the previous one, the strategy can open a long position. When the indicator turns downward, it can open a short position. Existing positions are closed when the indicator changes direction. After entering, additional positions are re-opened each time price advances by a user-defined step, allowing scaling into the trend.
This implementation is based on the original MetaTrader Expert Advisor Exp_Bezier_ReOpen.mq5 (ID 16883).
Details
- Indicator: Bezier curve constructed from the last
BPeriodprices and parameterTdefining curve tension. - Entry:
- Long: indicator slope turns up and current value is above previous value.
- Short: indicator slope turns down and current value is below previous value.
- Exit:
- Long: indicator slope turns down.
- Short: indicator slope turns up.
- Re-entry: after initial entry, an extra order is sent each time price moves
PriceStepaway from the last entry price, up toPosTotalorders. - Stops: optional stop-loss and take-profit defined in absolute price units.
Parameters
CandleType– candle timeframe used for calculations. Default: 4-hour.BPeriod– number of bars for Bezier calculation. Default: 8.T– Bezier curve tension (0..1). Default: 0.5.PriceType– price source for the indicator (close, open, high, low, median, typical, weighted). Default: weighted.PriceStep– price distance to send additional orders. Default: 300.PosTotal– maximum number of positions in scaling sequence. Default: 10.BuyPosOpen– allow opening long positions. Default: true.SellPosOpen– allow opening short positions. Default: true.BuyPosClose– allow closing longs on opposite signal. Default: true.SellPosClose– allow closing shorts on opposite signal. Default: true.StopLoss– stop-loss in price units. Default: 1000.TakeProfit– take-profit in price units. Default: 2000.
Filter Tags
- Category: Trend following
- Direction: Both
- Indicators: Custom
- Stops: Optional
- Complexity: Medium
- Timeframe: Medium-term
- Risk level: Moderate
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>
/// Bezier re-open strategy based on a custom Bezier curve calculation.
/// Opens positions when the Bezier value changes direction and re-enters
/// after price moves by a specified step.
/// </summary>
public class BezierReOpenStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bPeriod;
private readonly StrategyParam<decimal> _t;
private readonly StrategyParam<decimal> _priceStep;
private readonly StrategyParam<int> _posTotal;
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose;
private readonly StrategyParam<bool> _sellPosClose;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly List<decimal> _prices = new();
private decimal _prev1;
private decimal _prev2;
private int _orderCount;
private decimal _lastEntryPrice;
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of bars for Bezier calculation.
/// </summary>
public int BPeriod
{
get => _bPeriod.Value;
set => _bPeriod.Value = value;
}
/// <summary>
/// Bezier curve tension.
/// </summary>
public decimal T
{
get => _t.Value;
set => _t.Value = value;
}
/// <summary>
/// Price distance for additional entries.
/// </summary>
public decimal PriceStep
{
get => _priceStep.Value;
set => _priceStep.Value = value;
}
/// <summary>
/// Maximum number of positions in sequence.
/// </summary>
public int PosTotal
{
get => _posTotal.Value;
set => _posTotal.Value = value;
}
/// <summary>
/// Allow long entries.
/// </summary>
public bool BuyPosOpen
{
get => _buyPosOpen.Value;
set => _buyPosOpen.Value = value;
}
/// <summary>
/// Allow short entries.
/// </summary>
public bool SellPosOpen
{
get => _sellPosOpen.Value;
set => _sellPosOpen.Value = value;
}
/// <summary>
/// Close longs on opposite signal.
/// </summary>
public bool BuyPosClose
{
get => _buyPosClose.Value;
set => _buyPosClose.Value = value;
}
/// <summary>
/// Close shorts on opposite signal.
/// </summary>
public bool SellPosClose
{
get => _sellPosClose.Value;
set => _sellPosClose.Value = value;
}
/// <summary>
/// Stop-loss in price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take-profit in price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BezierReOpenStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_bPeriod = Param(nameof(BPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Bezier Period", "Number of bars for Bezier calculation", "Indicator");
_t = Param(nameof(T), 0.5m)
.SetDisplay("T", "Bezier curve tension", "Indicator");
_priceStep = Param(nameof(PriceStep), 300m)
.SetGreaterThanZero()
.SetDisplay("Price Step", "Price distance for additional entries", "Trading");
_posTotal = Param(nameof(PosTotal), 1)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum number of positions", "Trading");
_buyPosOpen = Param(nameof(BuyPosOpen), true)
.SetDisplay("Buy Enabled", "Allow long entries", "Trading");
_sellPosOpen = Param(nameof(SellPosOpen), true)
.SetDisplay("Sell Enabled", "Allow short entries", "Trading");
_buyPosClose = Param(nameof(BuyPosClose), true)
.SetDisplay("Close Long", "Close longs on opposite signal", "Trading");
_sellPosClose = Param(nameof(SellPosClose), true)
.SetDisplay("Close Short", "Close shorts on opposite signal", "Trading");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetDisplay("Stop Loss", "Stop-loss in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetDisplay("Take Profit", "Take-profit in price units", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prices.Clear();
_prev1 = 0m;
_prev2 = 0m;
_orderCount = 0;
_lastEntryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private decimal ComputeBezier()
{
var n = BPeriod;
if (_prices.Count < n + 1)
return 0m;
var t = (double)T;
double result = 0;
for (var i = 0; i <= n; i++)
{
var priceVal = (double)_prices[_prices.Count - n - 1 + i];
result += priceVal * Binomial(n, i) * Math.Pow(t, i) * Math.Pow(1 - t, n - i);
}
return (decimal)result;
}
private static double Binomial(int n, int k)
{
return Factorial(n) / (Factorial(k) * Factorial(n - k));
}
private static double Factorial(int value)
{
var res = 1d;
for (var j = 2; j <= value; j++)
res *= j;
return res;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m;
_prices.Add(price);
if (_prices.Count > BPeriod + 2)
_prices.RemoveAt(0);
var bezierValue = ComputeBezier();
if (bezierValue == 0m)
return;
_prev2 = _prev1;
_prev1 = bezierValue;
if (_prev2 == 0m)
return;
var openLong = false;
var openShort = false;
var closeLong = false;
var closeShort = false;
if (_prev1 > _prev2)
{
if (BuyPosOpen)
openLong = true;
if (SellPosClose && Position < 0)
closeShort = true;
}
else if (_prev1 < _prev2)
{
if (SellPosOpen)
openShort = true;
if (BuyPosClose && Position > 0)
closeLong = true;
}
if (closeLong)
SellMarket();
if (closeShort)
BuyMarket();
if (openLong && Position <= 0)
{
BuyMarket();
_orderCount = 1;
_lastEntryPrice = candle.ClosePrice;
return;
}
if (openShort && Position >= 0)
{
SellMarket();
_orderCount = 1;
_lastEntryPrice = candle.ClosePrice;
return;
}
if (Position > 0 && _orderCount < PosTotal)
{
if (candle.ClosePrice - _lastEntryPrice >= PriceStep)
{
BuyMarket();
_lastEntryPrice = candle.ClosePrice;
_orderCount++;
}
}
else if (Position < 0 && _orderCount < PosTotal)
{
if (_lastEntryPrice - candle.ClosePrice >= PriceStep)
{
SellMarket();
_lastEntryPrice = candle.ClosePrice;
_orderCount++;
}
}
CheckStops(candle.ClosePrice);
}
private void CheckStops(decimal price)
{
if (Position > 0)
{
if (StopLoss > 0 && price <= _lastEntryPrice - StopLoss)
SellMarket();
if (TakeProfit > 0 && price >= _lastEntryPrice + TakeProfit)
SellMarket();
}
else if (Position < 0)
{
if (StopLoss > 0 && price >= _lastEntryPrice + StopLoss)
BuyMarket();
if (TakeProfit > 0 && price <= _lastEntryPrice - TakeProfit)
BuyMarket();
}
}
}
import clr
import math
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
from StockSharp.Algo.Strategies import Strategy
class bezier_reopen_strategy(Strategy):
def __init__(self):
super(bezier_reopen_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._b_period = self.Param("BPeriod", 8) \
.SetDisplay("Bezier Period", "Number of bars for Bezier calculation", "Indicator")
self._t = self.Param("T", 0.5) \
.SetDisplay("T", "Bezier curve tension", "Indicator")
self._price_step_param = self.Param("PriceStep", 300.0) \
.SetDisplay("Price Step", "Price distance for additional entries", "Trading")
self._pos_total = self.Param("PosTotal", 1) \
.SetDisplay("Max Positions", "Maximum number of positions", "Trading")
self._buy_pos_open = self.Param("BuyPosOpen", True) \
.SetDisplay("Buy Enabled", "Allow long entries", "Trading")
self._sell_pos_open = self.Param("SellPosOpen", True) \
.SetDisplay("Sell Enabled", "Allow short entries", "Trading")
self._buy_pos_close = self.Param("BuyPosClose", True) \
.SetDisplay("Close Long", "Close longs on opposite signal", "Trading")
self._sell_pos_close = self.Param("SellPosClose", True) \
.SetDisplay("Close Short", "Close shorts on opposite signal", "Trading")
self._stop_loss = self.Param("StopLoss", 1000.0) \
.SetDisplay("Stop Loss", "Stop-loss in price units", "Risk")
self._take_profit = self.Param("TakeProfit", 2000.0) \
.SetDisplay("Take Profit", "Take-profit in price units", "Risk")
self._prices = []
self._prev1 = 0.0
self._prev2 = 0.0
self._order_count = 0
self._last_entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
@property
def b_period(self):
return self._b_period.Value
@property
def t_param(self):
return self._t.Value
@property
def price_step_param(self):
return self._price_step_param.Value
@property
def pos_total(self):
return self._pos_total.Value
@property
def buy_pos_open(self):
return self._buy_pos_open.Value
@property
def sell_pos_open(self):
return self._sell_pos_open.Value
@property
def buy_pos_close(self):
return self._buy_pos_close.Value
@property
def sell_pos_close(self):
return self._sell_pos_close.Value
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def take_profit(self):
return self._take_profit.Value
def OnReseted(self):
super(bezier_reopen_strategy, self).OnReseted()
self._prices = []
self._prev1 = 0.0
self._prev2 = 0.0
self._order_count = 0
self._last_entry_price = 0.0
def OnStarted2(self, time):
super(bezier_reopen_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _factorial(self, value):
res = 1.0
for j in range(2, value + 1):
res *= j
return res
def _binomial(self, n, k):
return self._factorial(n) / (self._factorial(k) * self._factorial(n - k))
def _compute_bezier(self):
n = self.b_period
if len(self._prices) < n + 1:
return 0.0
t = self.t_param
result = 0.0
for i in range(n + 1):
price_val = self._prices[len(self._prices) - n - 1 + i]
result += price_val * self._binomial(n, i) * (t ** i) * ((1 - t) ** (n - i))
return result
def _check_stops(self, price):
if self.Position > 0:
if self.stop_loss > 0 and price <= self._last_entry_price - self.stop_loss:
self.SellMarket()
if self.take_profit > 0 and price >= self._last_entry_price + self.take_profit:
self.SellMarket()
elif self.Position < 0:
if self.stop_loss > 0 and price >= self._last_entry_price + self.stop_loss:
self.BuyMarket()
if self.take_profit > 0 and price <= self._last_entry_price - self.take_profit:
self.BuyMarket()
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
price = (2.0 * float(candle.ClosePrice) + float(candle.HighPrice) + float(candle.LowPrice)) / 4.0
self._prices.append(price)
if len(self._prices) > self.b_period + 2:
self._prices.pop(0)
bezier_value = self._compute_bezier()
if bezier_value == 0.0:
return
self._prev2 = self._prev1
self._prev1 = bezier_value
if self._prev2 == 0.0:
return
open_long = False
open_short = False
close_long = False
close_short = False
if self._prev1 > self._prev2:
if self.buy_pos_open:
open_long = True
if self.sell_pos_close and self.Position < 0:
close_short = True
elif self._prev1 < self._prev2:
if self.sell_pos_open:
open_short = True
if self.buy_pos_close and self.Position > 0:
close_long = True
if close_long:
self.SellMarket()
if close_short:
self.BuyMarket()
if open_long and self.Position <= 0:
self.BuyMarket()
self._order_count = 1
self._last_entry_price = float(candle.ClosePrice)
return
if open_short and self.Position >= 0:
self.SellMarket()
self._order_count = 1
self._last_entry_price = float(candle.ClosePrice)
return
if self.Position > 0 and self._order_count < self.pos_total:
if float(candle.ClosePrice) - self._last_entry_price >= self.price_step_param:
self.BuyMarket()
self._last_entry_price = float(candle.ClosePrice)
self._order_count += 1
elif self.Position < 0 and self._order_count < self.pos_total:
if self._last_entry_price - float(candle.ClosePrice) >= self.price_step_param:
self.SellMarket()
self._last_entry_price = float(candle.ClosePrice)
self._order_count += 1
self._check_stops(float(candle.ClosePrice))
def CreateClone(self):
return bezier_reopen_strategy()