GitHub で見る
Constituents EA 戦略
この戦略は MQL/22595 の Constituents EA をStockSharpのハイレベルAPIにポーティングしたものです。特定の時間に最近の
レンジの周囲に2つの指値注文を配置する元のロジックを再現しながら、StockSharpの注文処理とリスク保護ヘルパーと互換性の
あるワークフローを維持します。
戦略の動作方法
- スケジュールされた有効化 – 各ローソク足の終わりに、戦略は次のバーが
StartHour に開始するかどうかを確認します。
その時だけ新しい指値注文が考慮されます。これは、設定された時間と一致する開始時刻を持つバーの誕生に反応したMetaTrader
コードを反映します。
- レンジ検出 – 前の
SearchDepth 本の完了ローソク足の中の最高値と最低値が Highest/Lowest インジケーターで
追跡されます。この2つの価格が注文配置に使用されるブレイクアウト/平均回帰レベルを定義します。
- 価格距離フィルター – 現在の最良のbid/askクォートが注文板フィードからストリーミングされます。クォートと候補価格との
距離が
MinOrderDistancePips 以上(PointValue を使って絶対価格に変換)の場合にのみ注文が配置されます。これは元の
フリーズレベル検証を再実装し、無効な指値注文を防ぎます。
- 注文スタイルの選択 –
PendingOrderMode は指値注文(最低値でのbuy limit、最高値でのsell limit)またはストップ注文
(最高値上のbuy stop、最低値下のsell stop)の間で選択します。MetaTraderスクリプトと同様に、両方の注文が同時に送信
されます。
- リスク保護 – 組み込みの
StartProtection ヘルパーは絶対価格ステップで表現されたストップロスとテイクプロフィット
レベルを添付します(StopLossPips/TakeProfitPips)。MinStopDistancePips に対する最小距離チェックは、保護注文が
シンボルのストップレベルを尊重しなければならないというMT5の要件を複製します。
- 注文管理 – 指値注文の一方が約定すると、反対の注文は即座にキャンセルされます。バーの間隔中、アクティブな注文が
存在する限り戦略は追加注文を配置しないため、ソースEAの動作と一致します。
パラメーター
| パラメーター |
説明 |
StartHour |
新しい指値注文のペアが作成される時間(0-23)。 |
SearchDepth |
最高値/最低値レンジの計算に使用する前の完了ローソク足の数。 |
PendingOrderMode |
Limit は平均回帰バリアントを複製、Stop はブレイクアウト注文を配置します。 |
StopLossPips |
pips単位のストップロス距離(PointValue で変換)。無効にするには0に設定。 |
TakeProfitPips |
pips単位のテイクプロフィット距離。無効にするには0に設定。 |
PointValue |
価格単位でのpip値。Security.PriceStep/MinStep から自動検出するには0に設定。 |
MinOrderDistancePips |
現在のbid/askと指値価格の間の最小許容距離。フリーズレベルチェックをモデル化します。 |
MinStopDistancePips |
ストップ/テイクの最小許容距離。StopsLevel チェックを反映します。 |
CandleType |
レンジ計算とスケジューリングロジックに使用する時間軸。 |
Strategy.Volume が注文サイズを制御します;BuyLimit、SellLimit、BuyStop、SellStop が注文を送信できるよう正に
維持してください。
使用方法
- 戦略を銘柄に添付し、取引したい時間軸に
CandleType を設定します。
- MT5の入力と同じように
StartHour と SearchDepth を設定します。ブローカーが注文と市場価格の間の最小距離を強制する
場合は Min*Pips の閾値を調整します。
- セキュリティのメタデータからの自動検出が不可能な場合(例えば合成商品の場合)は
PointValue を調整します。
StopLossPips と TakeProfitPips を元のEAと一致するように設定します。注文が約定したら保護モジュールが自動的に
ストップとターゲットを添付します。
- 正の
Volume を設定して戦略を開始します。ローソク足と注文板データをサブスクライブし、スケジュールされたバーで両方の
指値注文を配置し、一方のトレードが実行されたら反対の注文をキャンセルします。
元のEAとの違い
- MetaTraderの
MoneyFixedMargin リスクモード(パーセントベースのサイジング)はポートされていません。StockSharpユーザーは
Strategy.Volume を直接設定するか、外部のポジションサイジングモジュールで戦略をラップする必要があります。
- フリーズレベルとストップレベルのチェックは設定可能な
MinOrderDistancePips と MinStopDistancePips パラメーターを
通じて表現されます。これは同等の取引所メタデータが常にStockSharpを通じて利用できるわけではないためです。
- 注文配置は前のローソク足が閉じて次のバーが
StartHour に開始するときに発生します。これは新しいバーの誕生でトリガー
されたMT5実装と機能的に同一です。
- ソースコード内のすべてのコメントは英語に翻訳されており、外部ドキュメントは利便性のために複数の言語で利用可能です。
取引する予定の商品に合わせて距離と取引時間を調整します。スプレッドの広い市場では、ブローカーによる即時拒否を避けるために
より大きな MinOrderDistancePips またはpip値が必要になる場合があります。
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>
/// Constituents breakout strategy converted from the original MetaTrader expert advisor.
/// Detects the recent high/low range from N candles and enters with market orders
/// when price breaks above the high (buy) or below the low (sell).
/// Uses stop-loss, take-profit, and trailing stop for risk management.
/// </summary>
public class ConstituentsEAStrategy : Strategy
{
private readonly StrategyParam<int> _searchDepth;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<DataType> _candleType;
private Highest _highest = null!;
private Lowest _lowest = null!;
private decimal _pipSize;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _prevHigh;
private decimal _prevLow;
private bool _exitRequested;
/// <summary>
/// Number of completed candles used to determine the recent range.
/// </summary>
public int SearchDepth
{
get => _searchDepth.Value;
set => _searchDepth.Value = value;
}
/// <summary>
/// Stop loss distance expressed in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance expressed in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Working candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ConstituentsEaStrategy"/> class.
/// </summary>
public ConstituentsEAStrategy()
{
_searchDepth = Param(nameof(SearchDepth), 3)
.SetGreaterThanZero()
.SetDisplay("Search Depth", "Number of completed candles used to find extremes", "Setup");
_stopLossPips = Param(nameof(StopLossPips), 50m)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Stop loss distance expressed in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 100m)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Take profit distance expressed in pips", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Working timeframe used to evaluate highs/lows", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highest = null!;
_lowest = null!;
_pipSize = 0m;
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
_prevHigh = 0m;
_prevLow = 0m;
_exitRequested = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = CalculatePipSize();
_highest = new Highest { Length = SearchDepth };
_lowest = new Lowest { Length = SearchDepth };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Process indicators
var highValue = _highest.Process(new DecimalIndicatorValue(_highest, candle.HighPrice, candle.OpenTime) { IsFinal = true });
var lowValue = _lowest.Process(new DecimalIndicatorValue(_lowest, candle.LowPrice, candle.OpenTime) { IsFinal = true });
if (!_highest.IsFormed || !_lowest.IsFormed)
return;
var currentHigh = highValue.ToDecimal();
var currentLow = lowValue.ToDecimal();
// Manage existing position
if (Position != 0)
{
ManagePosition(candle);
// Update range for next trade
_prevHigh = currentHigh;
_prevLow = currentLow;
return;
}
// Check for breakout signals using previous range
if (_prevHigh > 0m && _prevLow > 0m)
{
// Breakout above the recent high -> buy
if (candle.ClosePrice > _prevHigh)
{
_entryPrice = candle.ClosePrice;
_exitRequested = false;
if (StopLossPips > 0m)
_stopPrice = _entryPrice - StopLossPips * _pipSize;
else
_stopPrice = null;
if (TakeProfitPips > 0m)
_takePrice = _entryPrice + TakeProfitPips * _pipSize;
else
_takePrice = null;
BuyMarket();
}
// Breakout below the recent low -> sell
else if (candle.ClosePrice < _prevLow)
{
_entryPrice = candle.ClosePrice;
_exitRequested = false;
if (StopLossPips > 0m)
_stopPrice = _entryPrice + StopLossPips * _pipSize;
else
_stopPrice = null;
if (TakeProfitPips > 0m)
_takePrice = _entryPrice - TakeProfitPips * _pipSize;
else
_takePrice = null;
SellMarket();
}
}
// Update range for next candle
_prevHigh = currentHigh;
_prevLow = currentLow;
}
private void ManagePosition(ICandleMessage candle)
{
if (_exitRequested)
return;
if (Position > 0)
{
// Check take profit
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
_exitRequested = true;
SellMarket();
return;
}
// Check stop loss
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
_exitRequested = true;
SellMarket();
return;
}
}
else if (Position < 0)
{
// Check take profit
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
_exitRequested = true;
BuyMarket();
return;
}
// Check stop loss
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
_exitRequested = true;
BuyMarket();
return;
}
}
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 0.01m;
return step;
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class constituents_ea_strategy(Strategy):
def __init__(self):
super(constituents_ea_strategy, self).__init__()
self._search_depth = self.Param("SearchDepth", 3) \
.SetDisplay("Search Depth", "Number of completed candles used to find extremes", "Setup")
self._stop_loss_pips = self.Param("StopLossPips", 50.0) \
.SetDisplay("Stop Loss pips", "Stop loss distance expressed in pips", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 100.0) \
.SetDisplay("Take Profit pips", "Take profit distance expressed in pips", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Working timeframe used to evaluate highs and lows", "General")
self._highest = None
self._lowest = None
self._pip_size = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._prev_high = 0.0
self._prev_low = 0.0
self._exit_requested = False
@property
def search_depth(self):
return self._search_depth.Value
@property
def stop_loss_pips(self):
return self._stop_loss_pips.Value
@property
def take_profit_pips(self):
return self._take_profit_pips.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(constituents_ea_strategy, self).OnReseted()
self._highest = None
self._lowest = None
self._pip_size = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._prev_high = 0.0
self._prev_low = 0.0
self._exit_requested = False
def OnStarted2(self, time):
super(constituents_ea_strategy, self).OnStarted2(time)
step = self.Security.PriceStep if self.Security is not None else None
self._pip_size = float(step) if step is not None and float(step) > 0 else 0.01
self._highest = Highest()
self._highest.Length = self.search_depth
self._lowest = Lowest()
self._lowest.Length = self.search_depth
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._highest, self._lowest, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, high_value, low_value):
if candle.State != CandleStates.Finished:
return
if not self._highest.IsFormed or not self._lowest.IsFormed:
return
current_high = float(high_value)
current_low = float(low_value)
if self.Position != 0:
self._manage_position(candle)
self._prev_high = current_high
self._prev_low = current_low
return
close = float(candle.ClosePrice)
sl_pips = float(self.stop_loss_pips)
tp_pips = float(self.take_profit_pips)
if self._prev_high > 0 and self._prev_low > 0:
if close > self._prev_high:
self._entry_price = close
self._exit_requested = False
self._stop_price = self._entry_price - sl_pips * self._pip_size if sl_pips > 0 else None
self._take_price = self._entry_price + tp_pips * self._pip_size if tp_pips > 0 else None
self.BuyMarket()
elif close < self._prev_low:
self._entry_price = close
self._exit_requested = False
self._stop_price = self._entry_price + sl_pips * self._pip_size if sl_pips > 0 else None
self._take_price = self._entry_price - tp_pips * self._pip_size if tp_pips > 0 else None
self.SellMarket()
self._prev_high = current_high
self._prev_low = current_low
def _manage_position(self, candle):
if self._exit_requested:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if self._take_price is not None and high >= self._take_price:
self._exit_requested = True
self.SellMarket()
return
if self._stop_price is not None and low <= self._stop_price:
self._exit_requested = True
self.SellMarket()
return
elif self.Position < 0:
if self._take_price is not None and low <= self._take_price:
self._exit_requested = True
self.BuyMarket()
return
if self._stop_price is not None and high >= self._stop_price:
self._exit_requested = True
self.BuyMarket()
return
def CreateClone(self):
return constituents_ea_strategy()