EMA(barabashkakvn 版本)策略
本策略由 MetaTrader 5 专家顾问 "EMA (barabashkakvn's edition)" 转换而来。系统监控两条以中位价计算的指数移动平均线,在确认均线交叉且价格对上一根 K 线极值出现小幅回撤后才开仓。止盈与止损均为“虚拟”距离,以点数(pip)形式表示。
核心思路
- 在指定周期上计算以
(High + Low) / 2为基础的 5 与 10 周期 EMA。 - 当快线与慢线交叉时,仅记录信号标志,并不立即下单。
- 等待价格从上一根 K 线的高点或低点回撤
MoveBackPips点,同时要求两条 EMA 的差值大于2 * pipSize。 - 满足条件后,按照交叉方向开仓。
- 开仓后根据点数距离维护虚拟止盈和止损,一旦价格触及便立即平仓。
这种流程完全复现了原 MQL 程序:原始 EA 使用变量 check 来标记交叉,然后检查 EMA 差值与回撤条件,最后通过虚拟报价判断是否触及止盈/止损。
指标与数据
- 以中位价计算的 EMA(5)。
- 以中位价计算的 EMA(10)。
- 前一根已完成 K 线的最高价与最低价,用于判断回撤。
- 所有计算均基于配置的
CandleType订阅并仅处理收盘完毕的 K 线。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
OrderVolume |
0.1 |
每次开仓的合约/手数。 |
VirtualProfitPips |
5 |
入场价到虚拟止盈的点数距离。 |
MoveBackPips |
3 |
交叉后所需的回撤点数,从上一根 K 线极值开始计算。 |
StopLossPips |
20 |
入场价到虚拟止损的点数距离。 |
PipSize |
0.0001 |
单个点对应的价格变动,需要根据交易品种调整。 |
FastLength |
5 |
快速 EMA 的周期。 |
SlowLength |
10 |
慢速 EMA 的周期。 |
CandleType |
TimeFrame(1m) |
用于计算的 K 线类型。 |
所有与点数相关的参数都会乘以 PipSize 转换成价格差。如果该值为 0 或负数,策略会尝试使用 Security.PriceStep(若交易所提供)。
交易逻辑
入场规则
- 信号记录:每次 EMA 发生交叉都会设置内部标志,但此时不下单。
- 做空条件:
- 信号标志已设置;
SlowEMA - FastEMA > 2 * pipSize;- 当前 K 线最高价 ≥ 上一根 K 线最低价 +
MoveBackPips * pipSize(价格自前低向上回撤)。
- 做多条件:
- 信号标志已设置;
FastEMA - SlowEMA > 2 * pipSize;- 当前 K 线最低价 ≤ 上一根 K 线最高价 -
MoveBackPips * pipSize(价格自前高向下回撤)。
开仓后会清除信号标志,避免重复进场。
离场规则
策略通过比较 K 线极值与虚拟距离来模拟原 EA 的 Bid/Ask 判断:
- 多单:
- 若最高价 ≥ 入场价 +
VirtualProfitPips * pipSize则止盈; - 若最低价 ≤ 入场价 -
StopLossPips * pipSize则止损。
- 若最高价 ≥ 入场价 +
- 空单:
- 若最低价 ≤ 入场价 -
VirtualProfitPips * pipSize则止盈; - 若最高价 ≥ 入场价 +
StopLossPips * pipSize则止损。
- 若最低价 ≤ 入场价 -
平仓后虚拟目标会被重置,等待下一次交叉。
实现细节
- 采用高层 API 的蜡烛订阅 (
SubscribeCandles),并可在图表上绘制 EMA 与成交点。 - 中位价直接由高低价计算,完全对齐 MetaTrader 的
PRICE_MEDIAN设定。 _hasCrossSignal与原脚本中的check作用一致,确保只有交叉+回撤同时满足才会下单。- 在
OnStarted中调用StartProtection(),即使退出逻辑为手动也可启用平台的风控机制。 - 代码注释全部为英文,并且未直接访问任何指标缓冲区。
使用建议
- 根据交易品种调整
PipSize(如日元货币对、指数、加密货币等报价制式差异)。 - 由于平仓判断依赖 K 线的最高/最低价,使用 1~5 分钟等较短周期更接近原 EA 的逐笔行为。
- 可通过优化器调整 EMA 周期、回撤点数和虚拟止盈止损距离以适配不同市场。
- 策略一次只持有一个方向的仓位,外部手动交易可能干扰虚拟止盈止损的跟踪。
风险提示
- 基于 K 线的极值判断可能遗漏盘中瞬间触价,必要时请使用更高分辨率的数据。
- 虚拟止损不会发送真实保护性委托,断线或滑点可能导致亏损超出预期。
- 均线交叉策略在震荡行情表现不佳,可根据需要叠加趋势或波动率过滤条件。
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()