GitHub で見る
オープンクローズ戦略 (ID 3996)
概要
この戦略は、MetaTrader 4 エキスパート open_close.mq4 を再現しています。これは単一の金融商品で動作し、最新のローソク足の始値と終値を前のローソク足と比較します。アクティブなポジションがない場合、強い 1 バーの動き (ギャップ アンド リバーサル パターン) がフェードアウトします。取引中、パターンが反転したとき、または変動損失保護のしきい値に違反したときにポジションを閉じます。
取引ロジック
エントリールール
- 前のローソク足が処理された場合にのみ取引されます(元の
Volume[0] == 1 ガード)。
- ロングエントリー: 現在のローソク足は前の始値より上で始まり**、**前の終値より下で閉じます。この戦略では、設定された量を市場で購入します。
- ショートエントリー: 現在のローソク足は前の始値よりも下で始まり**、**前の終値よりも上で閉じます。この戦略は市場で空売りされる。
いつでもアクティブにできるポジションは 1 つだけです。オープンポジションがクローズされるまで、新しいシグナルは無視されます。
終了ルール
- リスク保護: 変動損益は平均エントリー価格から測定されます。未実現損失が
MaximumRisk × Portfolio.CurrentValue を超える場合、ストラテジーは直ちにポジションをクローズします。元の MQL バージョンでは AccountMargin が使用されていましたが、ここでは利用可能な最良のポートフォリオ評価で近似しています。
- パターン反転:
- 次のローソク足が下降を続けるとロングポジションが終了します (
open < previous open と close < previous close)。
- 次のローソク足が上昇を続けるとショートポジションは終了します(
open > previous openとclose > previous close)。
ポジションサイズ
- デフォルトの注文サイズは
MaximumRisk から導出されます。この戦略は、利用可能なアカウントの値に MaximumRisk を乗算し、その結果を 1000 で除算し、AccountFreeMargin * MaximumRisk / 1000 の MetaTrader の計算を模倣します。
- アカウント情報が利用できない場合は、フォールバック
InitialVolume パラメータが使用されます。
- 複数回連続して取引に負けた後、ロットサイズは
volume × losses / DecreaseFactor だけ減少し、終了した取引の履歴で MetaTrader ループが再現されます。
- 数量を商品の取引量ステップと為替制限に合わせる前に、
0.1 ロットの最小取引可能量が適用されます。
パラメーター
| 名前 |
種類 |
デフォルト |
説明 |
InitialVolume |
decimal |
0.1 |
株式情報が利用できない場合に使用されるフォールバック ロット サイズ。 |
MaximumRisk |
decimal |
0.3 |
ポジションのサイジングと最大許容変動損失の両方を制御する口座価値の割合。 |
DecreaseFactor |
decimal |
100 |
複数回連続で負けたトレード後に適用される減額係数。 |
CandleType |
DataType |
15m 時間枠 |
パターンの評価に使用されるキャンドル シリーズ。 |
実装メモ
- このストラテジーは、選択されたローソク足シリーズをサブスクライブし、終了したローソク足のみを処理し、元のエキスパートの
Volume[0] > 1 条件と一致します。
- StockSharp は MetaTrader の
AccountProfit および AccountMargin 指標を公開していないため、変動損益はストラテジーの現在のポジションと最新の終値から推定されます。
- 連続損失は約定取引を通じて追跡されるため、
DecreaseFactor は取引履歴の元のループのように動作できます。
- ボリューム調整では、交換要件との互換性を維持するために、
Security.VolumeStep、MinVolume、および MaxVolume が考慮されます。
- チャート領域が視覚的なデバッグに使用できる場合、チャートにはローソク足と独自の取引が表示されます。
使用のヒント
- 元のエキスパートを調整するときに、MetaTrader で使用されるローソク足の間隔と一致するローソク足の間隔を選択します。
MaximumRisk と DecreaseFactor を調整して、ロットサイジング ルールの積極性を調整します。
- この戦略は逆張りであるため、単一バーのオーバーエクステンションやスナップバックの動きを頻繁に示す商品で最も優れたパフォーマンスを発揮します。
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>
/// Contrarian pattern strategy converted from the MetaTrader expert "open_close".
/// Evaluates relationships between consecutive candle opens and closes.
/// Buys when a bearish candle opens above the previous open (fading the move),
/// and sells when a bullish candle opens below the previous open.
/// </summary>
public class OpenCloseStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _ema;
private bool _hasPreviousCandle;
private decimal _previousOpen;
private decimal _previousClose;
public OpenCloseStrategy()
{
_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(4).TimeFrame())
.SetDisplay("Candle Type", "Time-frame used to evaluate the open/close pattern.", "Data");
Volume = 1;
}
/// <summary>
/// Stop loss distance in absolute points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance in absolute points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle series used to evaluate the pattern.
/// </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();
_hasPreviousCandle = false;
_previousOpen = 0m;
_previousClose = 0m;
_ema = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
_ema = new ExponentialMovingAverage { Length = 20 };
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);
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
var open = candle.OpenPrice;
var close = candle.ClosePrice;
if (!_hasPreviousCandle)
{
_previousOpen = open;
_previousClose = close;
_hasPreviousCandle = true;
return;
}
// Exit logic
if (Position > 0)
{
// Close long on bearish continuation
if (open < _previousOpen && close < _previousClose)
SellMarket(Position);
}
else if (Position < 0)
{
// Close short on bullish continuation
if (open > _previousOpen && close > _previousClose)
BuyMarket(Math.Abs(Position));
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_previousOpen = open;
_previousClose = close;
return;
}
// Entry logic
if (Position == 0)
{
// Buy: fade a bearish candle that opened above the previous open
if (open > _previousOpen && close < _previousClose)
{
BuyMarket(Volume);
}
// Sell: fade a bullish candle that opened below the previous open
else if (open < _previousOpen && close > _previousClose)
{
SellMarket(Volume);
}
}
_previousOpen = open;
_previousClose = close;
}
}
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class open_close_strategy(Strategy):
def __init__(self):
super(open_close_strategy, self).__init__()
self._sl_points = self.Param("StopLossPoints", 500.0).SetNotNegative().SetDisplay("Stop Loss", "Stop loss in absolute points", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 500.0).SetNotNegative().SetDisplay("Take Profit", "Take profit in absolute points", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Time-frame for open/close pattern.", "Data")
self.Volume = 1
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(open_close_strategy, self).OnReseted()
self._has_prev = False
self._prev_open = 0
self._prev_close = 0
def OnStarted2(self, time):
super(open_close_strategy, self).OnStarted2(time)
self._has_prev = False
self._prev_open = 0
self._prev_close = 0
ema = ExponentialMovingAverage()
ema.Length = 20
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(ema, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
tp = Unit(self._tp_points.Value, UnitTypes.Absolute) if self._tp_points.Value > 0 else None
sl = Unit(self._sl_points.Value, UnitTypes.Absolute) if self._sl_points.Value > 0 else None
if tp is not None or sl is not None:
self.StartProtection(tp, sl)
def OnProcess(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
open_price = candle.OpenPrice
close = candle.ClosePrice
if not self._has_prev:
self._prev_open = open_price
self._prev_close = close
self._has_prev = True
return
# Exit logic
if self.Position > 0:
if open_price < self._prev_open and close < self._prev_close:
self.SellMarket(self.Position)
elif self.Position < 0:
if open_price > self._prev_open and close > self._prev_close:
self.BuyMarket(Math.Abs(self.Position))
# Entry logic
if self.Position == 0:
if open_price > self._prev_open and close < self._prev_close:
self.BuyMarket(self.Volume)
elif open_price < self._prev_open and close > self._prev_close:
self.SellMarket(self.Volume)
self._prev_open = open_price
self._prev_close = close
def CreateClone(self):
return open_close_strategy()