押し目・反発戦略
概要
押し目・反発戦略はMQL5エキスパートアドバイザー「TST (barabashkakvn's edition)」のC#変換版です。CandleTypeパラメーターで指定された時間軸で単一のインストゥルメントを監視し、バーの範囲内に引き戻される強い動きを探します。強気バーが押し目しきい値を超えてその高値から下落したとき、戦略は買いに入り、同等の弱気の引き戻しは売りをトリガーします。この実装はStockSharpの高レベルローソク足サブスクリプションAPIを使用し、すべての保護注文を絶対的な価格オフセットに変換されるpip単位で管理します。
pip距離はインストゥルメントのPriceStepから計算されます。3桁または5桁の小数で価格が付くシンボルの場合、戦略は自動的にステップを10倍にしてMetaTraderのpipの定義に合わせます。すべてのポジションサイジングは戦略の基本Volumeプロパティから取得されます。
エントリーロジック
- 設定された
CandleTypeシリーズの完了したローソク足のみを処理します。 ReverseSignal = false(デフォルト)の場合:- ロング設定: ローソク足が始値を下回って閉じ、ローソク足の高値と終値の差が
RollbackRatePips(価格に変換)を超えます。これは価格が上方に拡張し、逆張りロングエントリーに適格なほど深く引き戻されたことを示します。 - ショート設定: ローソク足が始値を上回って閉じ、終値とローソク足の安値の差が
RollbackRatePipsを超えます。これは弱気サイドでのロングロジックを反映します。
- ロング設定: ローソク足が始値を下回って閉じ、ローソク足の高値と終値の差が
ReverseSignal = trueの場合、ロングとショートの条件の役割が入れ替わり、他のパラメーターを変更せずにトレーダーが方向を切り替えることができます。- 新しいエントリーは現在のポジションがフラットまたは反対方向の場合にのみ置かれます。実行されるボリュームは
Volume + |Position|に等しく、反対のポジションが新しいトレードを確立する前に閉じられます。
エグジットロジック
- エントリー時、戦略は設定されたpipオフセットに基づいてストップロスとテイクプロフィットレベルを保存します。ローソク足のレンジがレベルに触れると、ポジションは成行注文で閉じられます。
StopLossPips = 0またはTakeProfitPips = 0は対応する保護レベルを無効にします。- トレーリングロジックは変動利益が
TrailingStopPips + TrailingStepPips(価格単位)を超えると有効になります。- ロングトレードの場合、新しいレベルが前のストップより少なくとも
TrailingStepPips上にある場合はいつでも、ストップは最高価格 - TrailingStopPipsにラチェットします。 - ショートトレードの場合、新しいレベルが前のストップより少なくとも
TrailingStepPips下にある場合、ストップは最安値 + TrailingStopPipsにラチェットします。 - 市場が逆転してトレーリングストップを横切ると、ポジションはすぐに終了します。
- ロングトレードの場合、新しいレベルが前のストップより少なくとも
- ポジションが開いていない場合、古いデータを避けるためにすべての内部状態変数がクリアされます。
パラメーター
| パラメーター | 説明 | デフォルト |
|---|---|---|
CandleType |
シグナル計算に使用するローソク足シリーズ。 | 15分時間軸 |
StopLossPips |
pip単位の保護ストップの距離。無効化するにはゼロに設定。 | 30 |
TakeProfitPips |
pip単位のテイクプロフィットの距離。無効化するにはゼロに設定。 | 90 |
TrailingStopPips |
pip単位のトレーリングストップオフセット。トレーリングを無効化するにはゼロに設定。 | 1 |
TrailingStepPips |
トレーリングストップが再び動く前に必要な追加利益(pip単位)。トレーリングが有効な場合は正でなければなりません。 | 15 |
RollbackRatePips |
シグナルを検証するバーの極端から最小の引き戻し。 | 15 |
ReverseSignal |
エントリー方向を逆転させます(ロングシグナルがショートになり、その逆も同様)。 | false |
使用上のノート
- 戦略を開始する前に
Volumeプロパティを設定してください;これは各注文の取引量を定義します。 - トレーリングは
TrailingStopPips > 0とTrailingStepPips > 0を必要とします。この関係が違反された場合、戦略は起動時にエラーをスローします。 - オリジナルのエキスパートはアクティブなバー内のティックを評価していたため、C#ポートは完了したローソク足の高値/安値/終値を使用して同じ動作を近似します。この差異はほとんどのバックテストでは無視できるほど小さく、実装をStockSharpの高レベルAPIと一致させています。
- 戦略は単一の証券で動作します。複数のインストゥルメントを取引するには、別々の戦略インスタンスを作成してください。
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>
/// Rollback Rebound strategy that follows the TST (barabashkakvn's edition) expert advisor logic.
/// The strategy buys after a bullish bar pulls back from its high and sells after a bearish bar rebounds from its low.
/// Protective orders are managed in pips and include optional trailing logic with a rollback filter.
/// </summary>
public class RollbackReboundStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _trailingStepPips;
private readonly StrategyParam<decimal> _rollbackRatePips;
private readonly StrategyParam<bool> _reverseSignal;
private decimal _pipSize;
private decimal _stopLossOffset;
private decimal _takeProfitOffset;
private decimal _trailingStopOffset;
private decimal _trailingStepOffset;
private decimal _rollbackOffset;
private decimal _longEntryPrice;
private decimal _longStopPrice;
private decimal _longTakeProfitPrice;
private decimal _shortEntryPrice;
private decimal _shortStopPrice;
private decimal _shortTakeProfitPrice;
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop loss distance in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Additional profit in pips required before the trailing stop moves.
/// </summary>
public decimal TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Pullback threshold in pips to validate signals.
/// </summary>
public decimal RollbackRatePips
{
get => _rollbackRatePips.Value;
set => _rollbackRatePips.Value = value;
}
/// <summary>
/// Inverts entry direction.
/// </summary>
public bool ReverseSignal
{
get => _reverseSignal.Value;
set => _reverseSignal.Value = value;
}
/// <summary>
/// Initialize parameters with defaults derived from the original MQL expert.
/// </summary>
public RollbackReboundStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Candle series used for calculations.", "General");
_stopLossPips = Param(nameof(StopLossPips), 30m)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Distance of the protective stop in pips.", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 90m)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Distance of the take profit in pips.", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 20m)
.SetNotNegative()
.SetDisplay("Trailing Stop (pips)", "Trailing stop offset in pips.", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 15m)
.SetNotNegative()
.SetDisplay("Trailing Step (pips)", "Additional profit required before trailing adjusts.", "Risk");
_rollbackRatePips = Param(nameof(RollbackRatePips), 40m)
.SetNotNegative()
.SetDisplay("Rollback Threshold (pips)", "Minimum pullback from the bar extreme to trigger entries.", "Signal");
_reverseSignal = Param(nameof(ReverseSignal), false)
.SetDisplay("Reverse Signal", "Invert entry logic (buy becomes sell).", "Signal");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
_stopLossOffset = 0m;
_takeProfitOffset = 0m;
_trailingStopOffset = 0m;
_trailingStepOffset = 0m;
_rollbackOffset = 0m;
ResetLongState();
ResetShortState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Validate that trailing configuration matches the behaviour of the original expert.
if (TrailingStopPips > 0m && TrailingStepPips <= 0m)
throw new InvalidOperationException("Trailing step must be greater than zero when trailing stop is enabled.");
// Convert pip-based parameters into absolute price offsets.
_pipSize = Security?.PriceStep ?? 1m;
if (Security != null && (Security.Decimals == 3 || Security.Decimals == 5))
_pipSize *= 10m;
_stopLossOffset = StopLossPips * _pipSize;
_takeProfitOffset = TakeProfitPips * _pipSize;
_trailingStopOffset = TrailingStopPips * _pipSize;
_trailingStepOffset = TrailingStepPips * _pipSize;
_rollbackOffset = RollbackRatePips * _pipSize;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
// Work only with finished candles to emulate the IsNewBar check from the MQL expert.
if (candle.State != CandleStates.Finished)
return;
// Update trailing stops and exit conditions before generating new signals.
ManageOpenPosition(candle);
// Skip signal generation until the strategy is online and allowed to trade.
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Translate the rollback filters from the original EA using candle statistics.
var open = candle.OpenPrice;
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
var longCondition = open > close && high - close > _rollbackOffset;
var shortCondition = close > open && close - low > _rollbackOffset;
if (ReverseSignal)
{
(longCondition, shortCondition) = (shortCondition, longCondition);
}
if (longCondition && Position <= 0)
{
// Enter long when the rollback condition is met and the strategy is not already in a long position.
var volume = Volume + Math.Abs(Position);
if (volume <= 0m)
return;
BuyMarket(volume);
InitializeLongState(candle);
}
else if (shortCondition && Position >= 0)
{
// Enter short when the bearish rollback occurs and we are not currently short.
var volume = Volume + Math.Abs(Position);
if (volume <= 0m)
return;
SellMarket(volume);
InitializeShortState(candle);
}
}
private void ManageOpenPosition(ICandleMessage candle)
{
// Mirror the trailing and exit logic from the MetaTrader expert to keep behaviour identical.
if (Position > 0)
{
// Use the candle high as the most optimistic price for long positions.
var extreme = candle.HighPrice;
if (_longEntryPrice == 0m)
// Store the actual entry price once the trade is filled.
_longEntryPrice = candle.ClosePrice;
if (_trailingStopOffset > 0m)
{
// Apply the trailing algorithm for the active position.
// Move the stop only when profit exceeds trailing stop plus step, exactly as in the MQL code.
if (extreme - _longEntryPrice > _trailingStopOffset + _trailingStepOffset)
{
var threshold = extreme - (_trailingStopOffset + _trailingStepOffset);
if (_longStopPrice == 0m || _longStopPrice < threshold)
_longStopPrice = extreme - _trailingStopOffset;
}
}
if (_longTakeProfitPrice > 0m && candle.HighPrice >= _longTakeProfitPrice)
{
// Exit the long position once the take-profit level is touched.
SellMarket(Math.Abs(Position));
ResetLongState();
return;
}
if (_longStopPrice > 0m && candle.LowPrice <= _longStopPrice)
{
// Close the long position if the initial or trailing stop is triggered.
SellMarket(Math.Abs(Position));
ResetLongState();
return;
}
}
else if (Position < 0)
{
// Use the candle low as the best price in favour of the short position.
var extreme = candle.LowPrice;
if (_shortEntryPrice == 0m)
// Capture the short entry price after execution.
_shortEntryPrice = candle.ClosePrice;
if (_trailingStopOffset > 0m)
{
// Apply the trailing algorithm for short positions.
// Move the stop only when profit exceeds trailing stop plus step, exactly as in the MQL code.
if (_shortEntryPrice - extreme > _trailingStopOffset + _trailingStepOffset)
{
var threshold = extreme + (_trailingStopOffset + _trailingStepOffset);
if (_shortStopPrice == 0m || _shortStopPrice > threshold)
_shortStopPrice = extreme + _trailingStopOffset;
}
}
if (_shortTakeProfitPrice > 0m && candle.LowPrice <= _shortTakeProfitPrice)
{
// Exit the short position when the take-profit is hit.
BuyMarket(Math.Abs(Position));
ResetShortState();
return;
}
if (_shortStopPrice > 0m && candle.HighPrice >= _shortStopPrice)
{
// Cover the short position if the stop level is breached.
BuyMarket(Math.Abs(Position));
ResetShortState();
return;
}
}
else
{
// Clear cached levels when no position is open.
ResetLongState();
ResetShortState();
}
}
private void InitializeLongState(ICandleMessage candle)
{
// Clear short-side state because the strategy operates in netting mode.
ResetShortState();
var entry = candle.ClosePrice;
// Save reference prices for managing the long position.
_longEntryPrice = entry;
_longStopPrice = StopLossPips > 0m ? entry - _stopLossOffset : 0m;
_longTakeProfitPrice = TakeProfitPips > 0m ? entry + _takeProfitOffset : 0m;
}
private void InitializeShortState(ICandleMessage candle)
{
// Clear long-side state before opening a short position.
ResetLongState();
var entry = candle.ClosePrice;
// Store price references for the short position.
_shortEntryPrice = entry;
_shortStopPrice = StopLossPips > 0m ? entry + _stopLossOffset : 0m;
_shortTakeProfitPrice = TakeProfitPips > 0m ? entry - _takeProfitOffset : 0m;
}
private void ResetLongState()
{
_longEntryPrice = 0m;
_longStopPrice = 0m;
_longTakeProfitPrice = 0m;
}
private void ResetShortState()
{
_shortEntryPrice = 0m;
_shortStopPrice = 0m;
_shortTakeProfitPrice = 0m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class rollback_rebound_strategy(Strategy):
def __init__(self):
super(rollback_rebound_strategy, self).__init__()
self._sl_pips = self.Param("StopLossPips", 30.0).SetNotNegative().SetDisplay("Stop Loss (pips)", "SL distance", "Risk")
self._tp_pips = self.Param("TakeProfitPips", 90.0).SetNotNegative().SetDisplay("Take Profit (pips)", "TP distance", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 20.0).SetNotNegative().SetDisplay("Trailing Stop (pips)", "Trailing offset", "Risk")
self._trailing_step_pips = self.Param("TrailingStepPips", 15.0).SetNotNegative().SetDisplay("Trailing Step (pips)", "Trailing step", "Risk")
self._rollback_pips = self.Param("RollbackRatePips", 40.0).SetNotNegative().SetDisplay("Rollback Threshold (pips)", "Pullback threshold", "Signal")
self._reverse_signal = self.Param("ReverseSignal", False).SetDisplay("Reverse Signal", "Invert entry logic", "Signal")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(rollback_rebound_strategy, self).OnReseted()
self._pip_size = 0
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
def OnStarted2(self, time):
super(rollback_rebound_strategy, self).OnStarted2(time)
self._pip_size = 1.0
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
self._pip_size = float(self.Security.PriceStep)
if self.Security.Decimals == 3 or self.Security.Decimals == 5:
self._pip_size *= 10.0
self._sl_offset = self._sl_pips.Value * self._pip_size
self._tp_offset = self._tp_pips.Value * self._pip_size
self._trail_offset = self._trailing_stop_pips.Value * self._pip_size
self._trail_step_offset = self._trailing_step_pips.Value * self._pip_size
self._rollback_offset = self._rollback_pips.Value * self._pip_size
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
self._manage_position(candle)
o = candle.OpenPrice
c = candle.ClosePrice
h = candle.HighPrice
l = candle.LowPrice
long_cond = o > c and h - c > self._rollback_offset
short_cond = c > o and c - l > self._rollback_offset
if self._reverse_signal.Value:
long_cond, short_cond = short_cond, long_cond
if long_cond and self.Position <= 0:
vol = self.Volume + abs(self.Position)
if vol <= 0:
return
self.BuyMarket(vol)
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
self._long_entry = float(c)
self._long_stop = self._long_entry - self._sl_offset if self._sl_pips.Value > 0 else 0
self._long_tp = self._long_entry + self._tp_offset if self._tp_pips.Value > 0 else 0
elif short_cond and self.Position >= 0:
vol = self.Volume + abs(self.Position)
if vol <= 0:
return
self.SellMarket(vol)
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
self._short_entry = float(c)
self._short_stop = self._short_entry + self._sl_offset if self._sl_pips.Value > 0 else 0
self._short_tp = self._short_entry - self._tp_offset if self._tp_pips.Value > 0 else 0
def _manage_position(self, candle):
if self.Position > 0:
extreme = float(candle.HighPrice)
if self._long_entry == 0:
self._long_entry = float(candle.ClosePrice)
if self._trail_offset > 0 and self._long_entry > 0:
if extreme - self._long_entry > self._trail_offset + self._trail_step_offset:
threshold = extreme - (self._trail_offset + self._trail_step_offset)
if self._long_stop == 0 or self._long_stop < threshold:
self._long_stop = extreme - self._trail_offset
if self._long_tp > 0 and candle.HighPrice >= self._long_tp:
self.SellMarket(abs(self.Position))
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
return
if self._long_stop > 0 and candle.LowPrice <= self._long_stop:
self.SellMarket(abs(self.Position))
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
return
elif self.Position < 0:
extreme = float(candle.LowPrice)
if self._short_entry == 0:
self._short_entry = float(candle.ClosePrice)
if self._trail_offset > 0 and self._short_entry > 0:
if self._short_entry - extreme > self._trail_offset + self._trail_step_offset:
threshold = extreme + (self._trail_offset + self._trail_step_offset)
if self._short_stop == 0 or self._short_stop > threshold:
self._short_stop = extreme + self._trail_offset
if self._short_tp > 0 and candle.LowPrice <= self._short_tp:
self.BuyMarket(abs(self.Position))
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
return
if self._short_stop > 0 and candle.HighPrice >= self._short_stop:
self.BuyMarket(abs(self.Position))
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
return
else:
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
def CreateClone(self):
return rollback_rebound_strategy()