TST 回撤反转策略
概述
TST 回撤反转策略 是从 MetaTrader 4 专家顾问 TST.mq4 转换而来的策略,并使用 StockSharp 的高级 API 重写。策略寻找那些在创出当日极值之后价格大幅远离开盘价的 K 线,并押注价格将出现均值回归。系统可做多也可做空,并使用以价格最小变动为单位的固定止损和止盈。
信号逻辑
做多条件
- K 线收盘价低于开盘价(
Open > Close)。 - 最高价与收盘价之间的距离大于
GapPoints * PriceStep。 - 当前 K 线上尚未产生过交易。
满足条件时,策略会平掉任何空头仓位,并以
OrderVolume的数量(加上用于反向的头寸)市价买入。
- K 线收盘价低于开盘价(
做空条件
- K 线收盘价高于开盘价(
Close > Open)。 - 收盘价与最低价之间的距离大于
GapPoints * PriceStep。 - 当前 K 线上尚未产生过交易。
满足条件时,策略会平掉任何多头仓位,并以
OrderVolume的数量(加上用于反向的头寸)市价卖出。
- K 线收盘价高于开盘价(
仓位管理
- 每次入场都会根据
StopLossPoints和TakeProfitPoints参数计算固定的止损和止盈价位。 - 在每根完结的 K 线上,策略都会检测最高价和最低价是否触及这些水平:先检查止损,再检查止盈,一旦触发立即平仓。
- 平仓后会清空保存的风控水平,但会保留该根 K 线的时间戳,从而防止在同一根 K 线上再次开仓(对应 MT4 版本中的
NevBar()逻辑)。
参数
StopLossPoints(默认500):距入场价的止损距离,以最小变动单位表示。TakeProfitPoints(默认100):距入场价的止盈距离,以最小变动单位表示。GapPoints(默认500):K 线极值与收盘价之间形成信号所需的最小回撤距离。OrderVolume(默认0.1):每次新建仓位使用的交易数量。CandleType(默认1 小时):通过SubscribeCandles订阅的 K 线周期。
所有距离类参数都会乘以品种的 PriceStep;如果品种没有提供价格步长,则回退为 1。
实现细节
- 转换完全基于 StockSharp 的高级 API,无需创建自定义数据集合。
- 仅处理已经结束的 K 线,以便兼容 Strategy Designer,并通过完整的 K 线数据逼近 MT4 中的即时决策。
_lastSignalBarTime字段重现了NevBar()的保护逻辑,保证每根 K 线最多只有一次入场。- 在反向建仓时会一次性平掉已有仓位并开立新的方向,与 MT4 的下单方式一致。
- 止损和止盈在策略内部模拟,通过比较 K 线的高低价来判断是否触发。
实战建议
- 根据标的波动性调整
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()