Стратегия Bezier ReOpen
Стратегия Bezier ReOpen использует пользовательский индикатор на основе кривой Безье для определения направления тренда. Когда индикатор разворачивается вверх и его текущее значение выше предыдущего, открывается длинная позиция. Когда индикатор разворачивается вниз – короткая. Текущие позиции закрываются при смене направления индикатора. После входа дополнительные позиции открываются каждый раз, когда цена проходит заданный шаг от последней цены входа, что позволяет наращивать позицию.
Эта реализация основана на советнике MetaTrader Exp_Bezier_ReOpen.mq5 (ID 16883).
Детали
- Индикатор: кривая Безье, построенная по последним
BPeriodценам с параметромT. - Вход:
- Покупка: индикатор начинает расти и текущее значение выше предыдущего.
- Продажа: индикатор начинает падать и текущее значение ниже предыдущего.
- Выход:
- Покупка: индикатор начинает падать.
- Продажа: индикатор начинает расти.
- Повторный вход: после первого входа дополнительная заявка отправляется каждый раз, когда цена проходит
PriceStepот цены последнего входа, максимумPosTotalзаявок. - Стопы: необязательные стоп-лосс и тейк-профит в абсолютных ценовых единицах.
Параметры
CandleType– таймфрейм свечей. По умолчанию 4 часа.BPeriod– количество баров для расчёта Безье. По умолчанию 8.T– натяжение кривой Безье (0..1). По умолчанию 0.5.PriceType– источник цены (close, open, high, low, median, typical, weighted). По умолчанию weighted.PriceStep– расстояние в цене для повторного входа. По умолчанию 300.PosTotal– максимальное количество позиций в серии. По умолчанию 10.BuyPosOpen– разрешить покупки. По умолчанию true.SellPosOpen– разрешить продажи. По умолчанию true.BuyPosClose– закрывать покупки по обратному сигналу. По умолчанию true.SellPosClose– закрывать продажи по обратному сигналу. По умолчанию true.StopLoss– стоп-лосс в ценовых единицах. По умолчанию 1000.TakeProfit– тейк-профит в ценовых единицах. По умолчанию 2000.
Фильтры
- Категория: Следование тренду
- Направление: Оба
- Индикаторы: Пользовательский
- Стопы: Опционально
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Уровень риска: Средний
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()