TST Pullback Reversal — порт оригинального эксперта MetaTrader 4 TST.mq4, переписанный на высокоуровневом API StockSharp. Стратегия отслеживает свечи, где после формирования экстремума цена резко отошла от открытия, и пытается взять возврат к среднему. Работает в обе стороны и использует фиксированные стоп-лосс и тейк-профит в шагах цены.
Логика сигналов
Покупка
Свеча закрылась ниже цены открытия (Open > Close).
Разница между максимумом свечи и ценой закрытия превышает GapPoints * PriceStep.
На текущей свече еще не было сделок.
При выполнении условий закрывается короткая позиция (если была) и выставляется рыночная покупка на объем OrderVolume плюс объем, необходимый для переворота из шорта в лонг.
Продажа
Свеча закрылась выше цены открытия (Close > Open).
Разница между ценой закрытия и минимумом свечи превышает GapPoints * PriceStep.
На текущей свече еще не было сделок.
При выполнении условий закрывается длинная позиция (если была) и отправляется рыночная продажа на объем OrderVolume плюс объем для переворота из лонга в шорт.
Управление позицией
После входа сразу рассчитываются уровни стоп-лосса и тейк-профита на основе параметров StopLossPoints и TakeProfitPoints.
На каждой завершенной свече проверяются экстремумы: если минимум пробил стоп или максимум достиг тейка, позиция закрывается. Приоритет у стоп-лосса.
После выхода уровни очищаются, но сохраняется время свечи, чтобы не допустить повторного входа в ту же свечу (аналог функции NevBar() в исходнике MT4).
Параметры
StopLossPoints (по умолчанию 500): расстояние до защитного стопа в шагах цены.
TakeProfitPoints (по умолчанию 100): расстояние до тейк-профита в шагах цены.
GapPoints (по умолчанию 500): минимальный откат от экстремума до закрытия для формирования сигнала.
OrderVolume (по умолчанию 0.1): объем новой рыночной заявки.
CandleType (по умолчанию 1 час): таймфрейм свечей, которые подписывает стратегия через SubscribeCandles.
Все расстояния умножаются на PriceStep инструмента; если шаг цены не задан, используется значение 1.
Особенности реализации
Используется только высокоуровневый API StockSharp, без собственных коллекций данных.
Обработка идет по завершенным свечам, что обеспечивает совместимость с Designer и моделирует внутрисвечные решения советника.
Флаг _lastSignalBarTime повторяет механику NevBar() и ограничивает стратегию одним входом на свечу.
При смене направления стратегия автоматически закрывает противоположную позицию и открывает новую в одном рыночном приказе.
Стоп и тейк реализованы внутри кода стратегии и срабатывают при достижении уровней по значениям High/Low свечи.
Практические советы
Подбирайте GapPoints в зависимости от волатильности инструмента: большие значения уменьшают количество сигналов, но фильтруют мелкие откаты.
Если нужны более точные срабатывания стопов и тейков, используйте более короткий таймфрейм CandleType.
Рассмотрите добавление фильтров тренда, торговых сессий или объема перед запуском на реальном счете, чтобы снизить количество ложных входов.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// TST Pullback Reversal: buys after deep pullback from candle high,
/// sells after rally from candle low. Uses ATR for thresholds.
/// </summary>
public class TstStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _pullbackMultiplier;
private readonly StrategyParam<decimal> _stopMultiplier;
private readonly StrategyParam<decimal> _takeMultiplier;
private decimal _entryPrice;
private decimal _stopPrice;
private decimal _takePrice;
public TstStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
_pullbackMultiplier = Param(nameof(PullbackMultiplier), 0.5m)
.SetDisplay("Pullback Mult", "ATR multiplier for pullback threshold.", "Signals");
_stopMultiplier = Param(nameof(StopMultiplier), 2.0m)
.SetDisplay("Stop Mult", "ATR multiplier for stop loss.", "Risk");
_takeMultiplier = Param(nameof(TakeMultiplier), 1.0m)
.SetDisplay("Take Mult", "ATR multiplier for take profit.", "Risk");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal PullbackMultiplier
{
get => _pullbackMultiplier.Value;
set => _pullbackMultiplier.Value = value;
}
public decimal StopMultiplier
{
get => _stopMultiplier.Value;
set => _stopMultiplier.Value = value;
}
public decimal TakeMultiplier
{
get => _takeMultiplier.Value;
set => _takeMultiplier.Value = value;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_stopPrice = 0;
_takePrice = 0;
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var close = candle.ClosePrice;
var open = candle.OpenPrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
var threshold = atrVal * PullbackMultiplier;
var stopDist = atrVal * StopMultiplier;
var takeDist = atrVal * TakeMultiplier;
// Risk management
if (Position > 0)
{
if (_stopPrice > 0 && close <= _stopPrice)
{
SellMarket();
_entryPrice = 0;
_stopPrice = 0;
_takePrice = 0;
return;
}
if (_takePrice > 0 && close >= _takePrice)
{
SellMarket();
_entryPrice = 0;
_stopPrice = 0;
_takePrice = 0;
return;
}
}
else if (Position < 0)
{
if (_stopPrice > 0 && close >= _stopPrice)
{
BuyMarket();
_entryPrice = 0;
_stopPrice = 0;
_takePrice = 0;
return;
}
if (_takePrice > 0 && close <= _takePrice)
{
BuyMarket();
_entryPrice = 0;
_stopPrice = 0;
_takePrice = 0;
return;
}
}
// Entry: deep pullback from high = buy reversal
if (Position == 0)
{
if (open > close && high - close > threshold)
{
_entryPrice = close;
_stopPrice = close - stopDist;
_takePrice = close + takeDist;
BuyMarket();
}
else if (close > open && close - low > threshold)
{
_entryPrice = close;
_stopPrice = close + stopDist;
_takePrice = close - takeDist;
SellMarket();
}
}
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange
class tst_strategy(Strategy):
def __init__(self):
super(tst_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period", "Indicators")
self._pullback_multiplier = self.Param("PullbackMultiplier", 0.5) \
.SetDisplay("Pullback Mult", "ATR multiplier for pullback threshold", "Signals")
self._stop_multiplier = self.Param("StopMultiplier", 2.0) \
.SetDisplay("Stop Mult", "ATR multiplier for stop loss", "Risk")
self._take_multiplier = self.Param("TakeMultiplier", 1.0) \
.SetDisplay("Take Mult", "ATR multiplier for take profit", "Risk")
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def PullbackMultiplier(self):
return self._pullback_multiplier.Value
@property
def StopMultiplier(self):
return self._stop_multiplier.Value
@property
def TakeMultiplier(self):
return self._take_multiplier.Value
def OnStarted2(self, time):
super(tst_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
av = float(atr_val)
if av <= 0:
return
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
threshold = av * float(self.PullbackMultiplier)
stop_dist = av * float(self.StopMultiplier)
take_dist = av * float(self.TakeMultiplier)
# Risk management
if self.Position > 0:
if self._stop_price > 0 and close <= self._stop_price:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
return
if self._take_price > 0 and close >= self._take_price:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
return
elif self.Position < 0:
if self._stop_price > 0 and close >= self._stop_price:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
return
if self._take_price > 0 and close <= self._take_price:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
return
# Entry: deep pullback from high = buy reversal
if self.Position == 0:
if open_p > close and high - close > threshold:
self._entry_price = close
self._stop_price = close - stop_dist
self._take_price = close + take_dist
self.BuyMarket()
elif close > open_p and close - low > threshold:
self._entry_price = close
self._stop_price = close + stop_dist
self._take_price = close - take_dist
self.SellMarket()
def OnReseted(self):
super(tst_strategy, self).OnReseted()
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
def CreateClone(self):
return tst_strategy()