Стратегия Well Martin
Описание
Well Martin – это стратегия возврата к среднему, которая использует индикаторы Bollinger Bands и Average Directional Index (ADX). Покупка выполняется при уходе цены ниже нижней полосы Боллинджера при низком значении ADX. Продажа выполняется при выходе цены выше верхней полосы при тех же условиях. Выход из позиции происходит при достижении противоположной полосы или уровней тейк‑профита и стоп‑лосса.
Параметры
- CandleType – тип свечей для расчётов.
- BollingerPeriod – период полос Боллинджера.
- BollingerWidth – множитель стандартного отклонения.
- AdxPeriod – период индикатора ADX.
- AdxLevel – порог ADX; сделки открываются только если значение ниже порога.
- Volume – объём каждой сделки.
- TakeProfit – целевая прибыль в ценовых пунктах.
- StopLoss – допустимый убыток в ценовых пунктах.
Логика работы
- Подписка на поток свечей и расчёт индикаторов Bollinger Bands и ADX.
- При отсутствии позиции:
- Покупка, если цена закрытия ниже нижней полосы и ADX ниже порога.
- Продажа, если цена закрытия выше верхней полосы и ADX ниже порога.
- Запоминается направление последней сделки и новые входы разрешаются только в том же направлении либо при отсутствии сделок.
- При длинной позиции выход выполняется при касании верхней полосы, достижении тейк‑профита или стоп‑лосса.
- При короткой позиции выход выполняется при касании нижней полосы, достижении тейк‑профита или стоп‑лосса.
Примечания
В текущей реализации используется фиксированный объём. В оригинальном коде MQL объём увеличивался после убыточной сделки; эту функциональность можно добавить при необходимости.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Well Martin mean reversion strategy using Bollinger Bands.
/// Buys at lower band, sells at upper band.
/// </summary>
public class WellMartinStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerWidth;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private decimal _entryPrice;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int BollingerPeriod { get => _bollingerPeriod.Value; set => _bollingerPeriod.Value = value; }
public decimal BollingerWidth { get => _bollingerWidth.Value; set => _bollingerWidth.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public WellMartinStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_bollingerPeriod = Param(nameof(BollingerPeriod), 84)
.SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 1.8m)
.SetDisplay("Bollinger Width", "Bollinger Bands width", "Indicators");
_takeProfit = Param(nameof(TakeProfit), 1200m)
.SetDisplay("Take Profit", "Take profit in price units", "Risk");
_stopLoss = Param(nameof(StopLoss), 1400m)
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bb = new BollingerBands
{
Length = BollingerPeriod,
Width = BollingerWidth
};
SubscribeCandles(CandleType)
.BindEx(bb, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
{
if (candle.State != CandleStates.Finished)
return;
var bb = (IBollingerBandsValue)bbValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower)
return;
var close = candle.ClosePrice;
// Exit management
if (Position > 0)
{
var profit = close - _entryPrice;
if (close >= upper || (TakeProfit > 0 && profit >= TakeProfit) || (StopLoss > 0 && -profit >= StopLoss))
{
SellMarket();
return;
}
}
else if (Position < 0)
{
var profit = _entryPrice - close;
if (close <= lower || (TakeProfit > 0 && profit >= TakeProfit) || (StopLoss > 0 && -profit >= StopLoss))
{
BuyMarket();
return;
}
}
if (Position != 0)
return;
// Mean reversion: buy at lower band, sell at upper band
if (close < lower)
{
BuyMarket();
_entryPrice = close;
}
else if (close > upper)
{
SellMarket();
_entryPrice = close;
}
}
}
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
from StockSharp.Algo.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
class well_martin_strategy(Strategy):
def __init__(self):
super(well_martin_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._bollinger_period = self.Param("BollingerPeriod", 84) \
.SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators")
self._bollinger_width = self.Param("BollingerWidth", 1.8) \
.SetDisplay("Bollinger Width", "Bollinger Bands width", "Indicators")
self._take_profit = self.Param("TakeProfit", 1200.0) \
.SetDisplay("Take Profit", "Take profit in price units", "Risk")
self._stop_loss = self.Param("StopLoss", 1400.0) \
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk")
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@BollingerPeriod.setter
def BollingerPeriod(self, value):
self._bollinger_period.Value = value
@property
def BollingerWidth(self):
return self._bollinger_width.Value
@BollingerWidth.setter
def BollingerWidth(self, value):
self._bollinger_width.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
def OnStarted2(self, time):
super(well_martin_strategy, self).OnStarted2(time)
bb = BollingerBands()
bb.Length = self.BollingerPeriod
bb.Width = self.BollingerWidth
self.SubscribeCandles(self.CandleType) \
.BindEx(bb, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
upper_raw = bb_value.UpBand
lower_raw = bb_value.LowBand
if upper_raw is None or lower_raw is None:
return
upper = float(upper_raw)
lower = float(lower_raw)
close = float(candle.ClosePrice)
tp = float(self.TakeProfit)
sl = float(self.StopLoss)
if self.Position > 0:
profit = close - self._entry_price
if close >= upper or (tp > 0 and profit >= tp) or (sl > 0 and -profit >= sl):
self.SellMarket()
return
elif self.Position < 0:
profit = self._entry_price - close
if close <= lower or (tp > 0 and profit >= tp) or (sl > 0 and -profit >= sl):
self.BuyMarket()
return
if self.Position != 0:
return
if close < lower:
self.BuyMarket()
self._entry_price = close
elif close > upper:
self.SellMarket()
self._entry_price = close
def OnReseted(self):
super(well_martin_strategy, self).OnReseted()
self._entry_price = 0.0
def CreateClone(self):
return well_martin_strategy()