月曜日の典型的なブレイクアウト戦略
概要
月曜日の典型的なブレイクアウト戦略 は、MetaTrader エキスパート アドバイザー yi1ywioff50qr6 (リポジトリ ID 8187) の C# ポートです。オリジナルのロボットは時間足のローソク足を監視し、毎週月曜日に新しいセッションが前の足の典型的な価格 (high + low + close) / 3 を上回ったときにロングポジションをオープンします。この実装は、StockSharp の高レベル戦略フレームワーク内のエントリー ロジックを再現し、ポジションのサイジングとリスク管理のための詳細な構成パラメーターを追加します。
取引ロジック
- この戦略は、設定されたローソク足シリーズ (デフォルトでは時間ごと) をサブスクライブします。
- 完成した各キャンドルの開始時に、次のことがチェックされます。
- ろうそくは月曜日のものです。
- キャンドルの開始時間は、設定された Open Hour パラメーター (デフォルトは 09:00) と一致します。
- 未決済のポジションやアクティブな注文は存在しません。
- ローソク足の始値は、前のバーの通常の価格よりも高くなります。
- すべての条件が満たされると、ストラテジーは資金管理ブロックによって計算された数量で成行買い注文を送信します。保護的なストップロスとテイクプロフィットディスタンスは、
StartProtectionを通じて適用されます。
この戦略はショート ポジションをオープンすることはなく、対象となる月曜日のローソク足ごとに 1 回の取引のみを行います。
パラメーター
| パラメータ | 説明 | デフォルト |
|---|---|---|
FixedVolume |
エントリーのロットサイズ。株式スケーリング テーブルを有効にするには、0 に設定します。 |
0.1 |
OpenHour |
月曜日のシグナルが評価される取引セッション時間 (0 ~ 23)。 | 9 |
StopLossPoints |
保護停止の価格ポイントの距離。 0 は停止を無効にします。 |
50 |
TakeProfitPoints |
利益目標の価格点の距離。 0 はターゲットを無効にします。 |
20 |
InitialEquity |
株式ベースのロットスケーリングを有効にする株式しきい値。 | 600 |
EquityStep |
取引規模を拡大するには資本の増加が必要です。 | 300 |
InitialStepVolume |
資本が少なくとも InitialEquity の場合に使用されるロットサイズ。 |
0.4 |
VolumeStep |
EquityStep に達するごとに追加のロット サイズが追加されます。 |
0.2 |
CandleType |
戦略を推進するローソク足のデータ型 (デフォルトでは時間単位)。 | 1 hour time-frame |
資金管理
FixedVolumeがゼロより大きい場合、戦略は常に固定ロット サイズを使用します。FixedVolumeがゼロに等しい場合、戦略はポートフォリオの資本を検査します。- 資本が
InitialEquityを下回る場合、商品の最小ロットが使用されます。 - それ以外の場合、ボリュームは
InitialStepVolumeから始まり、追加の株式がEquityStepごとにVolumeStepずつ増加します。 - 最終的なボリュームは、楽器の最小値とステップの制約に合わせて調整されます。
- 資本が
リスク管理
StartProtection は OnStarted 中にアクティブ化されます。ストップロスとテイクプロフィットの距離は、商品 PriceStep を使用してポイントから価格オフセットに自動的に変換されます。そのコンポーネントを無効にするには、いずれかの距離をゼロに設定します。
使用上の注意
- オリジナルの EA は時間足のローソク足用に設計されています。より低い時間枠では、同じ時間の複数の月曜日のローソク足が生成される可能性があります。ポートはキャンドルごとに 1 つのエントリーの動作を維持し、ポジションがオープンしている間は追加のシグナルを無視します。
- 動的サイジング ブロックが有効になっている場合は、ポートフォリオ情報 (
Portfolio.CurrentValue) が利用可能であることを確認してください。 - この戦略には、成行注文を実行するためのレベル 1 データと、設定された
CandleTypeの対応するキャンドル サブスクリプションが必要です。
変換メモ
- MQL のマジックナンバー フィルタリングは、StockSharp の位置と順序のチェック (
PositionおよびActiveOrders) に置き換えられます。 - 時間比較では、ローソク足の始値からの
DateTimeOffsetと.ToLocalTime()を利用して、チャートの時間と一致させます。 - 保護注文は、手動注文ではなく、高レベルの
StartProtectionヘルパーによって処理されます。
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;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the MetaTrader strategy yi1ywioff50qr6 (ID 8187).
/// Buys on Monday when the hourly open breaks above the prior bar's typical price.
/// Applies equity-based position sizing when the fixed lot is disabled.
/// </summary>
public class MondayTypicalBreakoutStrategy : Strategy
{
private readonly StrategyParam<decimal> _fixedVolume;
private readonly StrategyParam<int> _openHour;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<decimal> _initialEquity;
private readonly StrategyParam<decimal> _equityStep;
private readonly StrategyParam<decimal> _initialStepVolume;
private readonly StrategyParam<decimal> _volumeStep;
private readonly StrategyParam<DataType> _candleType;
private ICandleMessage _previousCandle;
private DateTimeOffset? _lastSignalTime;
private decimal _priceStep;
/// <summary>
/// Initializes parameters to mirror the MQL expert defaults.
/// </summary>
public MondayTypicalBreakoutStrategy()
{
_fixedVolume = Param(nameof(FixedVolume), 0.1m)
.SetNotNegative()
.SetDisplay("Fixed Volume", "Lot size used for entries (set to 0 to enable equity scaling)", "Risk");
_openHour = Param(nameof(OpenHour), 9)
.SetRange(0, 23)
.SetDisplay("Open Hour", "Hour of the session to evaluate Monday breakout entries", "Session");
_stopLossPoints = Param(nameof(StopLossPoints), 50)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 20)
.SetNotNegative()
.SetDisplay("Take Profit (points)", "Profit target distance expressed in price points", "Risk");
_initialEquity = Param(nameof(InitialEquity), 600m)
.SetGreaterThanZero()
.SetDisplay("Initial Equity", "Account equity threshold that triggers the first scaling tier", "Money Management");
_equityStep = Param(nameof(EquityStep), 300m)
.SetGreaterThanZero()
.SetDisplay("Equity Step", "Incremental equity required to raise the position size", "Money Management");
_initialStepVolume = Param(nameof(InitialStepVolume), 0.4m)
.SetGreaterThanZero()
.SetDisplay("Initial Step Volume", "Lot size used once the equity threshold is met", "Money Management");
_volumeStep = Param(nameof(VolumeStep), 0.2m)
.SetNotNegative()
.SetDisplay("Volume Step", "Additional lot size added for each equity step", "Money Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for detecting the Monday breakout", "General");
}
/// <summary>
/// Fixed lot size used for entries (set to zero to enable scaling).
/// </summary>
public decimal FixedVolume
{
get => _fixedVolume.Value;
set => _fixedVolume.Value = value;
}
/// <summary>
/// Hour (0-23) when Monday entries are evaluated.
/// </summary>
public int OpenHour
{
get => _openHour.Value;
set => _openHour.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in price points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in price points.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Minimum equity required before the scaling table becomes active.
/// </summary>
public decimal InitialEquity
{
get => _initialEquity.Value;
set => _initialEquity.Value = value;
}
/// <summary>
/// Equity increment that increases the trade size by <see cref="VolumeStep"/>.
/// </summary>
public decimal EquityStep
{
get => _equityStep.Value;
set => _equityStep.Value = value;
}
/// <summary>
/// Volume applied when the first equity threshold is met.
/// </summary>
public decimal InitialStepVolume
{
get => _initialStepVolume.Value;
set => _initialStepVolume.Value = value;
}
/// <summary>
/// Additional volume added for each equity tier.
/// </summary>
public decimal VolumeStep
{
get => _volumeStep.Value;
set => _volumeStep.Value = value;
}
/// <summary>
/// Candle type used for detecting breakout conditions.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousCandle = null;
_lastSignalTime = null;
_priceStep = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 0m;
if (_priceStep <= 0m)
_priceStep = 0.0001m;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
if (TakeProfitPoints > 0 || StopLossPoints > 0)
{
var takeDistance = TakeProfitPoints > 0
? new Unit(TakeProfitPoints * _priceStep, UnitTypes.Absolute)
: new Unit(0m);
var stopDistance = StopLossPoints > 0
? new Unit(StopLossPoints * _priceStep, UnitTypes.Absolute)
: new Unit(0m);
StartProtection(takeProfit: takeDistance, stopLoss: stopDistance);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var previous = _previousCandle;
_previousCandle = candle;
if (previous is null)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0m)
return;
var candleTime = candle.OpenTime.ToLocalTime();
if (candleTime.DayOfWeek != DayOfWeek.Monday)
return;
if (candleTime.Hour != OpenHour)
return;
if (_lastSignalTime is DateTimeOffset last && last == candle.OpenTime)
return;
var typicalPrice = (previous.HighPrice + previous.LowPrice + previous.ClosePrice) / 3m;
if (candle.OpenPrice <= typicalPrice)
return;
var volume = CalculateOrderVolume();
volume = AlignVolume(volume);
if (volume <= 0m)
return;
BuyMarket(volume);
_lastSignalTime = candle.OpenTime;
}
private decimal CalculateOrderVolume()
{
var fixedVolume = FixedVolume;
if (fixedVolume > 0m)
return fixedVolume;
var security = Security;
var portfolio = Portfolio;
var minVolume = security?.MinVolume ?? 0m;
if (minVolume <= 0m)
minVolume = 0.01m;
var equity = portfolio?.CurrentValue ?? portfolio?.BeginValue ?? 0m;
if (equity <= 0m)
return minVolume;
if (equity < InitialEquity)
return minVolume;
if (EquityStep <= 0m)
return InitialStepVolume;
var stepsDecimal = (equity - InitialEquity) / EquityStep;
if (stepsDecimal < 0m)
stepsDecimal = 0m;
var steps = (int)Math.Floor(stepsDecimal);
var dynamicVolume = InitialStepVolume + VolumeStep * steps;
if (dynamicVolume < minVolume)
dynamicVolume = minVolume;
return dynamicVolume;
}
private decimal AlignVolume(decimal volume)
{
var security = Security;
if (security == null)
return volume;
var minVolume = security.MinVolume ?? 0m;
var maxVolume = security.MaxVolume ?? 0m;
var step = security.VolumeStep ?? 0m;
if (minVolume > 0m && volume < minVolume)
volume = minVolume;
if (maxVolume > 0m && volume > maxVolume)
volume = maxVolume;
if (step > 0m)
{
var steps = Math.Round(volume / step, MidpointRounding.AwayFromZero);
volume = steps * step;
}
return 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, DayOfWeek
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
class monday_typical_breakout_strategy(Strategy):
def __init__(self):
super(monday_typical_breakout_strategy, self).__init__()
self._fixed_volume = self.Param("FixedVolume", 0.1) \
.SetDisplay("Fixed Volume", "Lot size used for entries (set to 0 to enable equity scaling)", "Risk")
self._open_hour = self.Param("OpenHour", 9) \
.SetDisplay("Open Hour", "Hour of the session to evaluate Monday breakout entries", "Session")
self._stop_loss_points = self.Param("StopLossPoints", 50) \
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 20) \
.SetDisplay("Take Profit (points)", "Profit target distance expressed in price points", "Risk")
self._initial_equity = self.Param("InitialEquity", 600.0) \
.SetDisplay("Initial Equity", "Account equity threshold that triggers the first scaling tier", "Money Management")
self._equity_step = self.Param("EquityStep", 300.0) \
.SetDisplay("Equity Step", "Incremental equity required to raise the position size", "Money Management")
self._initial_step_volume = self.Param("InitialStepVolume", 0.4) \
.SetDisplay("Initial Step Volume", "Lot size used once the equity threshold is met", "Money Management")
self._volume_step = self.Param("VolumeStep", 0.2) \
.SetDisplay("Volume Step", "Additional lot size added for each equity step", "Money Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe used for detecting the Monday breakout", "General")
self._previous_candle = None
self._last_signal_time = None
self._price_step = 0.0
@property
def FixedVolume(self):
return self._fixed_volume.Value
@FixedVolume.setter
def FixedVolume(self, value):
self._fixed_volume.Value = value
@property
def OpenHour(self):
return self._open_hour.Value
@OpenHour.setter
def OpenHour(self, value):
self._open_hour.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def InitialEquity(self):
return self._initial_equity.Value
@InitialEquity.setter
def InitialEquity(self, value):
self._initial_equity.Value = value
@property
def EquityStep(self):
return self._equity_step.Value
@EquityStep.setter
def EquityStep(self, value):
self._equity_step.Value = value
@property
def InitialStepVolume(self):
return self._initial_step_volume.Value
@InitialStepVolume.setter
def InitialStepVolume(self, value):
self._initial_step_volume.Value = value
@property
def VolumeStep(self):
return self._volume_step.Value
@VolumeStep.setter
def VolumeStep(self, value):
self._volume_step.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(monday_typical_breakout_strategy, self).OnStarted2(time)
ps = self.Security.PriceStep if self.Security is not None else 0
self._price_step = float(ps) if ps is not None and float(ps) > 0 else 0.0001
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
tp = int(self.TakeProfitPoints)
sl = int(self.StopLossPoints)
if tp > 0 or sl > 0:
take_dist = Unit(tp * self._price_step, UnitTypes.Absolute) if tp > 0 else Unit(0)
stop_dist = Unit(sl * self._price_step, UnitTypes.Absolute) if sl > 0 else Unit(0)
self.StartProtection(takeProfit=take_dist, stopLoss=stop_dist)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
previous = self._previous_candle
self._previous_candle = candle
if previous is None:
return
if self.Position != 0:
return
candle_time = candle.OpenTime.ToLocalTime()
if candle_time.DayOfWeek != DayOfWeek.Monday:
return
if candle_time.Hour != self.OpenHour:
return
if self._last_signal_time is not None and self._last_signal_time == candle.OpenTime:
return
typical_price = (float(previous.HighPrice) + float(previous.LowPrice) + float(previous.ClosePrice)) / 3.0
if float(candle.OpenPrice) <= typical_price:
return
volume = self._calculate_order_volume()
if volume <= 0:
return
self.BuyMarket(volume)
self._last_signal_time = candle.OpenTime
def _calculate_order_volume(self):
fixed_vol = float(self.FixedVolume)
if fixed_vol > 0:
return fixed_vol
min_volume = 0.01
if self.Security is not None and self.Security.MinVolume is not None and float(self.Security.MinVolume) > 0:
min_volume = float(self.Security.MinVolume)
equity = 0.0
if self.Portfolio is not None:
cv = self.Portfolio.CurrentValue
bv = self.Portfolio.BeginValue
if cv is not None and float(cv) > 0:
equity = float(cv)
elif bv is not None and float(bv) > 0:
equity = float(bv)
if equity <= 0:
return min_volume
initial_eq = float(self.InitialEquity)
if equity < initial_eq:
return min_volume
eq_step = float(self.EquityStep)
if eq_step <= 0:
return float(self.InitialStepVolume)
steps_decimal = (equity - initial_eq) / eq_step
if steps_decimal < 0:
steps_decimal = 0
steps = int(steps_decimal)
dynamic_volume = float(self.InitialStepVolume) + float(self.VolumeStep) * steps
if dynamic_volume < min_volume:
dynamic_volume = min_volume
return dynamic_volume
def OnReseted(self):
super(monday_typical_breakout_strategy, self).OnReseted()
self._previous_candle = None
self._last_signal_time = None
self._price_step = 0.0
def CreateClone(self):
return monday_typical_breakout_strategy()