サポートとレジスタンスのブレイクアウト戦略
概要
この戦略は、最近のサポートとレジスタンスのブレイクアウトを長期的な EMA トレンド フィルターと組み合わせることで、「SupportResistTrade」MetaTrader エキスパートを再現します。取引は、価格が Donchian チャネル境界を突破し、かつ ローソク足が長い指数移動平均の同じ側で始まる場合にのみ開始されます。リスクは、即時の保護停止と +10、+20、+30 ポイントで利益を確定する 3 ステップのトレーリング ルーチンを通じて管理されます。
データと指標
- プライマリ フィード: 単一のキャンドル サブスクリプション (デフォルトの 1 分の時間枠、
CandleTypeを通じて構成可能)。 - サポート/レジスタンス: 長さ
RangeLengthのDonchianChannels(デフォルトは 55) で、最近の範囲の最高値と最低値を追跡します。 - トレンド フィルター: ローソク足の
ExponentialMovingAverageは期間EmaPeriodで始まります (デフォルトは 500)。 EMA を超える価格のロングと、EMA を下回る価格のショートのみが受け入れられます。
取引ロジック
- 市場分析: 終了したローソク足ごとに、Donchian の範囲と EMA が更新されます。上のバンドはレジスタンスとして扱われ、下のバンドはサポートとして扱われます。
- エントリー条件:
- ロング: ローソク足は抵抗線を上回って終了し、かつ 始値は EMA を上回りました。既存のショート注文はすべてクローズされ、ロング成行注文が送信されます。
- ショート: ローソク足の終値はサポートを下回っており、かつ 始値は EMA を下回っていました。既存の買いは決済され、売りの成行注文が送信されます。
- 初期ストップ: 約定後、MQL のストップロス動作を反映して、最新のサポート (ロングの場合) またはレジスタンス (ショートの場合) にストップ注文が発注されます。
- 終了ロジック:
- 取引に利益があり、終値が更新されたサポート/レジスタンスバンドを超えた場合、ポジションは市場でクローズされ、EAの手動終了条件と一致します。
- 保護停止はアクティブなままであるため、突然の逆転は自動的に捕捉されます。
トレーリングストップ
段階的なトレーリング メカニズムにより、EA の 3 つの OrderModify 呼び出しが再現されます。
| 利益閾値(ポイント) | 新しい停止距離 (ポイント) | 説明 |
| --- | --- | --- |
| >= 20 | 10 | ロングストップはエントリー + 10 ポイントにジャンプします (エントリーまでのショートストップ - 10)。 |
| >= 40 | 20 | ストップはエントリー +/- 20 ポイントに移動します。 |
| >= 60 | 30 | 最後のステップで 30 ポイントの利益をロックします。 |
このロジックではストップが緩むことはありません。ロングの場合はストップは上方にのみ移動でき、ショートの場合は下方にのみ移動できます。
リスク管理
- すべてのストップはネイティブのストップ注文 (
SellStop/BuyStop) として実装されるため、ストラテジーが一時的に切断された場合でもブローカーが実行を処理します。 - この戦略はネットポジションベースで機能します。新しいシグナルはすべて、新たな取引を確立する前に反対方向に閉じます。
パラメーター
| 名前 | デフォルト | 説明 |
|---|---|---|
RangeLength |
55 |
サポート (低) とレジスタンス (高) の計算に使用されるローソク足の数。 |
EmaPeriod |
500 |
ローソク足の始値に適用される EMA トレンド フィルターの期間。 |
CandleType |
1 Minute |
すべての計算に使用されるローソク足シリーズ (他の時間枠に切り替えることができます)。 |
注意事項
- このコードは、インジケーター バインディングとローソク足サブスクリプションのみを使用して、高レベルの StockSharp API に対して書かれています。
- Python ポートは提供されません。
CSフォルダーには唯一の実装が含まれています。
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>
/// Support and resistance breakout strategy with EMA trend filter.
/// Buys above resistance during bullish trends and sells below support during bearish trends.
/// Manually tracks highest high and lowest low over N candles for support/resistance.
/// </summary>
public class SupportResistanceBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _rangeLength;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _ema;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal _support;
private decimal _resistance;
private decimal? _entryPrice;
/// <summary>
/// Number of candles used to compute support and resistance.
/// </summary>
public int RangeLength
{
get => _rangeLength.Value;
set => _rangeLength.Value = value;
}
/// <summary>
/// EMA length used as the trend filter.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Stop loss in absolute points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit in absolute points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public SupportResistanceBreakoutStrategy()
{
_rangeLength = Param(nameof(RangeLength), 55)
.SetGreaterThanZero()
.SetDisplay("Range Length", "Candles used to form support/resistance", "General")
.SetOptimize(20, 100, 5);
_emaPeriod = Param(nameof(EmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Length of the EMA trend filter", "General")
.SetOptimize(20, 200, 10);
_stopLossPoints = Param(nameof(StopLossPoints), 500m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss in absolute points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
.SetNotNegative()
.SetDisplay("Take Profit", "Take profit in absolute points", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series", "General");
Volume = 1;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ema = null;
_highs.Clear();
_lows.Clear();
_support = 0m;
_resistance = 0m;
_entryPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
if (tp != null || sl != null)
StartProtection(tp, sl);
base.OnStarted2(time);
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position != 0 && _entryPrice == null)
_entryPrice = trade.Trade.Price;
if (Position == 0)
_entryPrice = null;
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
// Track highs and lows manually
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > RangeLength)
_highs.RemoveAt(0);
if (_lows.Count > RangeLength)
_lows.RemoveAt(0);
if (_highs.Count < RangeLength)
return;
// Compute support/resistance from previous bars (exclude current)
var prevResistance = _resistance;
var prevSupport = _support;
decimal maxHigh = decimal.MinValue;
decimal minLow = decimal.MaxValue;
// Use bars 0..N-2 (exclude last which is current)
for (int i = 0; i < _highs.Count - 1; i++)
{
if (_highs[i] > maxHigh) maxHigh = _highs[i];
if (_lows[i] < minLow) minLow = _lows[i];
}
_resistance = maxHigh;
_support = minLow;
// Determine trend from EMA
var isBullish = candle.ClosePrice > emaValue;
var isBearish = candle.ClosePrice < emaValue;
// Exit: close longs if price falls back below support while in profit
if (Position > 0 && _entryPrice is decimal entryLong)
{
if (candle.ClosePrice - entryLong > 0 && candle.ClosePrice < _support)
{
SellMarket(Position);
return;
}
}
else if (Position < 0 && _entryPrice is decimal entryShort)
{
if (entryShort - candle.ClosePrice > 0 && candle.ClosePrice > _resistance)
{
BuyMarket(Math.Abs(Position));
return;
}
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Entry: breakout above resistance in bullish trend
if (isBullish && Position <= 0 && candle.ClosePrice > _resistance && _resistance > 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
}
// Entry: breakdown below support in bearish trend
else if (isBearish && Position >= 0 && candle.ClosePrice < _support && _support > 0)
{
if (Position > 0)
SellMarket(Position);
SellMarket(Volume);
}
}
}
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, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage
class support_resistance_breakout_strategy(Strategy):
def __init__(self):
super(support_resistance_breakout_strategy, self).__init__()
self._range_length = self.Param("RangeLength", 55) \
.SetDisplay("Range Length", "Candles used to form support/resistance", "General")
self._ema_period = self.Param("EmaPeriod", 50) \
.SetDisplay("EMA Period", "Length of the EMA trend filter", "General")
self._stop_loss_points = self.Param("StopLossPoints", 500.0) \
.SetDisplay("Stop Loss", "Stop loss in absolute points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 500.0) \
.SetDisplay("Take Profit", "Take profit in absolute points", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Primary candle series", "General")
self.Volume = 1
self._highs = []
self._lows = []
self._support = 0.0
self._resistance = 0.0
self._entry_price = None
@property
def RangeLength(self):
return self._range_length.Value
@property
def EmaPeriod(self):
return self._ema_period.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, self.ProcessCandle).Start()
tp = float(self.TakeProfitPoints)
sl = float(self.StopLossPoints)
tp_unit = Unit(tp, UnitTypes.Absolute) if tp > 0 else None
sl_unit = Unit(sl, UnitTypes.Absolute) if sl > 0 else None
if tp_unit is not None or sl_unit is not None:
self.StartProtection(tp_unit, sl_unit)
super(support_resistance_breakout_strategy, self).OnStarted2(time)
def ProcessCandle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
ema_value = float(ema_value)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._highs.append(high)
self._lows.append(low)
rl = int(self.RangeLength)
if len(self._highs) > rl:
self._highs.pop(0)
if len(self._lows) > rl:
self._lows.pop(0)
if len(self._highs) < rl:
return
max_high = max(self._highs[:-1])
min_low = min(self._lows[:-1])
self._resistance = max_high
self._support = min_low
is_bullish = close > ema_value
is_bearish = close < ema_value
pos = float(self.Position)
if pos > 0 and self._entry_price is not None:
if close - self._entry_price > 0 and close < self._support:
self.SellMarket(self.Position)
return
elif pos < 0 and self._entry_price is not None:
if self._entry_price - close > 0 and close > self._resistance:
self.BuyMarket(Math.Abs(self.Position))
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if is_bullish and pos <= 0 and close > self._resistance and self._resistance > 0:
if pos < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
elif is_bearish and pos >= 0 and close < self._support and self._support > 0:
if pos > 0:
self.SellMarket(self.Position)
self.SellMarket(self.Volume)
def OnOwnTradeReceived(self, trade):
super(support_resistance_breakout_strategy, self).OnOwnTradeReceived(trade)
if self.Position != 0 and self._entry_price is None:
self._entry_price = float(trade.Trade.Price)
if self.Position == 0:
self._entry_price = None
def OnReseted(self):
super(support_resistance_breakout_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._support = 0.0
self._resistance = 0.0
self._entry_price = None
def CreateClone(self):
return support_resistance_breakout_strategy()