EMA (barabashkakvn Edition)戦略
MetaTrader 5エキスパートアドバイザー「EMA (barabashkakvn's edition)」から変換されました。このシステムは中間値価格で計算された2つの指数移動平均のクロスオーバーを取引し、ピップで表現された仮想テイクプロフィット/ストップロスレベルを使用します。ポジションは確認されたクロスオーバーと前のローソク足の極値への小さな押し目の後にのみ開かれます。
核心的な考え方
- 選択した時間軸で中間値価格の5期間と10期間EMAを追跡する。
- 高速EMAが低速EMAをクロスした時、即座に取引するのではなく保留シグナルをセットする。
- EMAスプレッドが
2 * pipSizeを超えながら、価格が前のローソク足の極値からMoveBackPips押し目する。
- 押し目が発生したらクロスオーバーの方向にエントリーする。
- エントリー価格からピップ単位で測定された仮想ターゲットとストップでオープンポジションを管理する。
この動作は元のMQL実装を反映しています:エキスパートアドバイザーはクロスオーバーフラグ(check)を待ってから、トレードをトリガーするためにEMAスプレッドプラス前のローソク足に対する価格押し目を要求しました。エグジットルールも、指定された距離にbid/askが触れると仮定してポジションを閉じる「仮想」アプローチに従います。
インジケーターとデータ
- 中間値価格(high + low)/ 2での5期間EMA。
- 中間値価格での10期間EMA。
- 押し目確認のための前の完成ローソク足の高値/安値。
- すべての処理は設定された
CandleTypeサブスクリプションからの完成ローソク足を使用。
パラメーター
| パラメーター |
デフォルト |
説明 |
OrderVolume |
0.1 |
各エントリーのロット/契約での取引ボリューム。 |
VirtualProfitPips |
5 |
エントリー価格と仮想テイクプロフィット間の距離(ピップ単位)。 |
MoveBackPips |
3 |
クロスオーバー後に必要な押し目、前のローソク足の極値から計測。 |
StopLossPips |
20 |
エントリー価格と仮想ストップロス間の距離(ピップ単位)。 |
PipSize |
0.0001 |
価格単位で表現されたピップサイズ。異なるピップ定義のシンボルを取引する際に上書き。 |
FastLength |
5 |
高速EMAの長さ。 |
SlowLength |
10 |
低速EMAの長さ。 |
CandleType |
TimeFrame(1m) |
計算に使用されるローソク足ソース。 |
すべてのピップベースの値はpipValue = PipSizeを使用して価格距離に変換されます。パラメーターがゼロまたは負の数のままの場合、戦略はSecurity.PriceStepにフォールバックします(ボードから提供される場合)。
トレードロジック
エントリー条件
- シグナルのアーミング: クロスオーバーが発生するたびに保留シグナルを保存(
FastEMAがSlowEMAを上回りクロスまたはその逆)。まだトレードは配置されません。
- ショートエントリー: 要件
- 保留シグナルが存在する。
SlowEMA - FastEMA > 2 * pipSize。
- 現在のローソク足の高値 ≥ 前のローソク足の安値 +
MoveBackPips * pipSize(価格が前の安値から上に押し目した)。
- ロングエントリー: 要件
- 保留シグナルが存在する。
FastEMA - SlowEMA > 2 * pipSize。
- 現在のローソク足の安値 ≤ 前のローソク足の高値 -
MoveBackPips * pipSize(価格が前の高値から下に押し目した)。
ポジションを開いた後、重複エントリーを避けるために保留フラグはリセットされます。
エグジット条件
仮想ターゲットはローソク足の極値をプリセットの距離と比較することでMQL動作をエミュレートします:
- ロングポジション:
- ローソク足の高値 ≥ エントリー価格 +
VirtualProfitPips * pipSizeの場合クローズ。
- ローソク足の安値 ≤ エントリー価格 -
StopLossPips * pipSizeの場合クローズ。
- ショートポジション:
- ローソク足の安値 ≤ エントリー価格 -
VirtualProfitPips * pipSizeの場合クローズ。
- ローソク足の高値 ≥ エントリー価格 +
StopLossPips * pipSizeの場合クローズ。
エグジット後、仮想レベルはリセットされ、戦略は次のクロスオーバーを待ちます。
実装に関する注意事項
- 高レベルのローソク足サブスクリプション(
SubscribeCandles)を使用し、オプションのチャートエリアにEMAとトレードを描画します。
- 中間値価格はMetaTraderの
PRICE_MEDIANに一致するようにローソク足の高値/安値から直接計算されます。
- クロスオーバーフラグ(
_hasCrossSignal)は元のcheck変数を再現し、クロスオーバーと押し目の確認後にのみトレードが発生することを保証します。
StartProtection()はOnStartedで呼び出され、戦略がエグジットを手動で処理しても組み込みのリスク監視を有効にします。
- コードはリクエストに従ってすべてのコメントを英語で保持し、インジケーターバッファに直接アクセスせずに完成ローソク足のみに依存します。
使用上のヒント
- 非標準のピップ定義のインストゥルメント(例:JPYペア、インデックス、暗号通貨の相場)で操作する場合は
PipSizeを調整してください。
- エグジットはローソク足の極値に依存するため、より短い時間軸(1〜5分)を使用すると元のティックベースのエキスパートに近い動作が維持されます。
- 最適化は提供されたパラメーターメタデータを使用してEMAの長さ、ピップ距離、押し目値を探索できます。
- 戦略は一度に1つのポジションを取引します。同じ銘柄の外部ポジションは仮想エグジット追跡に干渉する可能性があります。
リスク
- ローソク足ベースのシミュレーションは仮想レベルのイントラバータッチを見逃す可能性があります。精度が重要な場合は高解像度データを検討してください。
- 仮想エグジットは実際の保護注文を配置しないため、ライブ取引での切断やスリッページは予想より大きな損失につながる可能性があります。
- どのクロスオーバーシステムでも、レンジ相場ではパフォーマンスが低下します。必要に応じてフィルターと組み合わせてください。
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>
/// EMA crossover strategy with virtual take profit and stop loss distances.
/// Converted from the MQL5 expert "EMA (barabashkakvn's edition)".
/// </summary>
public class EmaBarabashkakvnEditionStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _virtualProfitPips;
private readonly StrategyParam<int> _moveBackPips;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<decimal> _pipSize;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private bool _hasCrossSignal;
private decimal? _prevFast;
private decimal? _prevSlow;
private decimal? _prevHigh;
private decimal? _prevLow;
private decimal? _entryPrice;
private decimal? _virtualTarget;
private decimal? _virtualStop;
/// <summary>
/// Order volume in lots.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Virtual take profit distance in pips.
/// </summary>
public int VirtualProfitPips
{
get => _virtualProfitPips.Value;
set => _virtualProfitPips.Value = value;
}
/// <summary>
/// Retracement distance after a crossover in pips.
/// </summary>
public int MoveBackPips
{
get => _moveBackPips.Value;
set => _moveBackPips.Value = value;
}
/// <summary>
/// Virtual stop loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Pip size in price units.
/// </summary>
public decimal PipSize
{
get => _pipSize.Value;
set => _pipSize.Value = value;
}
/// <summary>
/// Fast EMA length applied to median price.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow EMA length applied to median price.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="EmaBarabashkakvnEditionStrategy"/>.
/// </summary>
public EmaBarabashkakvnEditionStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume in lots", "Trading")
.SetOptimize(0.05m, 1m, 0.05m);
_virtualProfitPips = Param(nameof(VirtualProfitPips), 5)
.SetGreaterThanZero()
.SetDisplay("Virtual Profit", "Take profit distance in pips", "Risk")
.SetOptimize(2, 20, 1);
_moveBackPips = Param(nameof(MoveBackPips), 3)
.SetGreaterThanZero()
.SetDisplay("Move Back", "Retracement after crossover in pips", "Entries")
.SetOptimize(1, 10, 1);
_stopLossPips = Param(nameof(StopLossPips), 20)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Virtual stop loss distance in pips", "Risk")
.SetOptimize(10, 60, 2);
_pipSize = Param(nameof(PipSize), 0.0001m)
.SetGreaterThanZero()
.SetDisplay("Pip Size", "Instrument pip size in price units", "General");
_fastLength = Param(nameof(FastLength), 5)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length on median price", "Indicators")
.SetOptimize(3, 15, 1);
_slowLength = Param(nameof(SlowLength), 10)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length on median price", "Indicators")
.SetOptimize(8, 40, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Source candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = default;
_slowEma = default;
_hasCrossSignal = false;
_prevFast = default;
_prevSlow = default;
_prevHigh = default;
_prevLow = default;
_entryPrice = default;
_virtualTarget = default;
_virtualStop = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new EMA { Length = FastLength };
_slowEma = new EMA { Length = SlowLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastEma);
DrawIndicator(area, _slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Calculate median price as in the original expert (PRICE_MEDIAN).
var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;
// Update EMA values using the median price.
var fastValue = _fastEma.Process(new DecimalIndicatorValue(_fastEma, medianPrice, candle.OpenTime) { IsFinal = true });
var slowValue = _slowEma.Process(new DecimalIndicatorValue(_slowEma, medianPrice, candle.OpenTime) { IsFinal = true });
if (!_fastEma.IsFormed || !_slowEma.IsFormed)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevFast = fastValue.ToDecimal();
_prevSlow = slowValue.ToDecimal();
return;
}
var fast = fastValue.ToDecimal();
var slow = slowValue.ToDecimal();
if (_prevFast is decimal prevFast && _prevSlow is decimal prevSlow)
{
var bullishCross = prevFast <= prevSlow && fast > slow;
var bearishCross = prevFast >= prevSlow && fast < slow;
if (bullishCross || bearishCross)
_hasCrossSignal = true;
}
_prevFast = fast;
_prevSlow = slow;
var pipValue = PipSize;
if (pipValue <= 0m)
pipValue = Security?.PriceStep ?? 0.0001m;
var moveBackPrice = MoveBackPips * pipValue;
var profitDistance = VirtualProfitPips * pipValue;
var stopDistance = StopLossPips * pipValue;
if (Position == 0 && _hasCrossSignal && _prevHigh is decimal prevHigh && _prevLow is decimal prevLow)
{
var bearishSpread = slow - fast;
var bullishSpread = fast - slow;
var bearishReady = bearishSpread > 2m * pipValue && candle.HighPrice >= prevLow + moveBackPrice;
var bullishReady = bullishSpread > 2m * pipValue && candle.LowPrice <= prevHigh - moveBackPrice;
if (bearishReady)
{
// Enter short after bearish cross and retracement above the previous low.
_entryPrice = candle.ClosePrice;
_virtualTarget = _entryPrice - profitDistance;
_virtualStop = _entryPrice + stopDistance;
SellMarket();
_hasCrossSignal = false;
}
else if (bullishReady)
{
// Enter long after bullish cross and retracement below the previous high.
_entryPrice = candle.ClosePrice;
_virtualTarget = _entryPrice + profitDistance;
_virtualStop = _entryPrice - stopDistance;
BuyMarket();
_hasCrossSignal = false;
}
}
else if (Position != 0 && _entryPrice is decimal && _virtualTarget is decimal target && _virtualStop is decimal stop)
{
if (Position > 0)
{
// Long position: use high for profit target and low for stop.
var hitTarget = candle.HighPrice >= target;
var hitStop = candle.LowPrice <= stop;
if (hitTarget || hitStop)
{
SellMarket();
_hasCrossSignal = false;
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
}
else if (Position < 0)
{
// Short position: use low for profit target and high for stop.
var hitTarget = candle.LowPrice <= target;
var hitStop = candle.HighPrice >= stop;
if (hitTarget || hitStop)
{
BuyMarket();
_hasCrossSignal = false;
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
}
}
if (Position == 0)
{
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class ema_barabashkakvn_edition_strategy(Strategy):
def __init__(self):
super(ema_barabashkakvn_edition_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1)
self._virtual_profit_pips = self.Param("VirtualProfitPips", 5)
self._move_back_pips = self.Param("MoveBackPips", 3)
self._stop_loss_pips = self.Param("StopLossPips", 20)
self._pip_size = self.Param("PipSize", 0.0001)
self._fast_length = self.Param("FastLength", 5)
self._slow_length = self.Param("SlowLength", 10)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
@property
def OrderVolume(self):
return self._order_volume.Value
@OrderVolume.setter
def OrderVolume(self, value):
self._order_volume.Value = value
@property
def VirtualProfitPips(self):
return self._virtual_profit_pips.Value
@VirtualProfitPips.setter
def VirtualProfitPips(self, value):
self._virtual_profit_pips.Value = value
@property
def MoveBackPips(self):
return self._move_back_pips.Value
@MoveBackPips.setter
def MoveBackPips(self, value):
self._move_back_pips.Value = value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@StopLossPips.setter
def StopLossPips(self, value):
self._stop_loss_pips.Value = value
@property
def PipSize(self):
return self._pip_size.Value
@PipSize.setter
def PipSize(self, value):
self._pip_size.Value = value
@property
def FastLength(self):
return self._fast_length.Value
@FastLength.setter
def FastLength(self, value):
self._fast_length.Value = value
@property
def SlowLength(self):
return self._slow_length.Value
@SlowLength.setter
def SlowLength(self, value):
self._slow_length.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(ema_barabashkakvn_edition_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowLength
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
median_price = (high + low) / 2.0
fast_result = process_float(self._fast_ema, median_price, candle.OpenTime, True)
slow_result = process_float(self._slow_ema, median_price, candle.OpenTime, True)
if not self._fast_ema.IsFormed or not self._slow_ema.IsFormed:
self._prev_high = high
self._prev_low = low
self._prev_fast = float(fast_result)
self._prev_slow = float(slow_result)
return
fast = float(fast_result)
slow = float(slow_result)
if self._prev_fast is not None and self._prev_slow is not None:
bullish_cross = self._prev_fast <= self._prev_slow and fast > slow
bearish_cross = self._prev_fast >= self._prev_slow and fast < slow
if bullish_cross or bearish_cross:
self._has_cross_signal = True
self._prev_fast = fast
self._prev_slow = slow
pip_value = float(self.PipSize)
if pip_value <= 0.0:
pip_value = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0001
move_back_price = int(self.MoveBackPips) * pip_value
profit_distance = int(self.VirtualProfitPips) * pip_value
stop_distance = int(self.StopLossPips) * pip_value
if self.Position == 0 and self._has_cross_signal and self._prev_high is not None and self._prev_low is not None:
bearish_spread = slow - fast
bullish_spread = fast - slow
bearish_ready = bearish_spread > 2.0 * pip_value and high >= self._prev_low + move_back_price
bullish_ready = bullish_spread > 2.0 * pip_value and low <= self._prev_high - move_back_price
if bearish_ready:
self._entry_price = close
self._virtual_target = self._entry_price - profit_distance
self._virtual_stop = self._entry_price + stop_distance
self.SellMarket()
self._has_cross_signal = False
elif bullish_ready:
self._entry_price = close
self._virtual_target = self._entry_price + profit_distance
self._virtual_stop = self._entry_price - stop_distance
self.BuyMarket()
self._has_cross_signal = False
elif self.Position != 0 and self._entry_price is not None and self._virtual_target is not None and self._virtual_stop is not None:
if self.Position > 0:
hit_target = high >= self._virtual_target
hit_stop = low <= self._virtual_stop
if hit_target or hit_stop:
self.SellMarket()
self._has_cross_signal = False
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
elif self.Position < 0:
hit_target = low <= self._virtual_target
hit_stop = high >= self._virtual_stop
if hit_target or hit_stop:
self.BuyMarket()
self._has_cross_signal = False
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
if self.Position == 0:
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
self._prev_high = high
self._prev_low = low
def OnReseted(self):
super(ema_barabashkakvn_edition_strategy, self).OnReseted()
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
def CreateClone(self):
return ema_barabashkakvn_edition_strategy()