在 GitHub 上查看
Exp i-KlPrice Vol 策略
概述
该策略是 MetaTrader 专家 Exp_i-KlPrice_Vol.mq5 的 C# 版本。策略重新实现 KlPrice 振荡器:它衡量价格相对于波动带的位置,将结果乘以蜡烛成交量,并根据自适应阈值的颜色切换生成信号。每个方向都包含两个独立的持仓槽位,以模拟原始 EA 中两个 magic 编号的行为。
指标逻辑
- 根据
AppliedPrice 设定(收盘价、开盘价、中值、Demark 等)转换蜡烛价格。
- 使用
PriceMaMethod 与 PriceMaLength 定义的移动平均对转换后的价格进行平滑。
- 蜡烛振幅 (
High - Low) 通过 RangeMaMethod/RangeMaLength 进行平滑,用作动态通道宽度。
- 基础 KlPrice 振荡器按公式
100 * (Price - (MA - RangeMA)) / (2 * RangeMA) - 50 计算。
- 将振荡器乘以所选的体量源(
AppliedVolume.Tick 或 AppliedVolume.Real)。
- 长度为
SmoothingLength 的 Jurik 平滑同时作用于振荡器和体量,得到两条自适应序列。
- 将平滑后的体量分别乘以
HighLevel2、HighLevel1、LowLevel1、LowLevel2,得到自适应阈值。
- 振荡器颜色由平滑值与阈值比较得出:
- 4 – 高于
HighLevel2 * volume,表明强势多头。
- 3 – 介于上限和极值之间。
- 2 – 上下阈值之间的中性区。
- 1 – 介于下阈值与零轴之间。
- 0 – 低于
LowLevel2 * volume,表明强势空头。
交易规则
- 读取
SignalBar 指定的历史蜡烛颜色(通常是上一根已完成的蜡烛)以及再早一根的颜色。
- 多头开仓:
- 槽位 1:颜色从 4 下降到 4 以下且
AllowLongEntry 为 true 时触发。
- 槽位 2:颜色从 3 下降到 3 以下时触发。
- 空头开仓:
- 槽位 1:颜色从 0 上升到 0 以上且
AllowShortEntry 为 true 时触发。
- 槽位 2:颜色从 1 上升到 1 以上时触发。
- 多头平仓:若较早的颜色为 0 或 1,并且
AllowLongExit 已启用,则关闭全部多头头寸。
- 空头平仓:若较早的颜色为 4 或 3,并且
AllowShortExit 已启用,则关闭全部空头头寸。
- 每个槽位都会记录最近的信号时间,防止在同一根蜡烛上重复下单。当
StopLossPoints 或 TakeProfitPoints 大于零时,通过 StartProtection 启用止损/止盈保护。
参数
| 名称 |
类型 |
默认值 |
说明 |
PrimaryVolume |
decimal |
0.1 |
第一槽位的下单量。 |
SecondaryVolume |
decimal |
0.2 |
第二槽位的下单量。 |
StopLossPoints |
int |
1000 |
止损距离(以最小价位计)。 |
TakeProfitPoints |
int |
2000 |
止盈距离(以最小价位计)。 |
AllowLongEntry |
bool |
true |
是否允许开多。 |
AllowShortEntry |
bool |
true |
是否允许开空。 |
AllowLongExit |
bool |
true |
是否在出现空头颜色时平多。 |
AllowShortExit |
bool |
true |
是否在出现多头颜色时平空。 |
CandleType |
DataType |
H8 |
指标使用的蜡烛时间框架。 |
PriceMaMethod |
SmoothMethod |
Sma |
价格平滑所用的移动平均类型。 |
PriceMaLength |
int |
100 |
价格平滑周期。 |
PriceMaPhase |
int |
15 |
Jurik 滤波器的相位参数。 |
RangeMaMethod |
SmoothMethod |
Jjma |
蜡烛振幅的平滑方法。 |
RangeMaLength |
int |
20 |
振幅平滑周期。 |
RangeMaPhase |
int |
100 |
振幅平滑的相位参数。 |
SmoothingLength |
int |
20 |
对振荡器和体量执行 Jurik 平滑的长度。 |
AppliedPrice |
AppliedPrice |
Close |
振荡器计算所用的价格来源。 |
VolumeType |
AppliedVolume |
Tick |
乘以振荡器的体量来源。 |
HighLevel2 |
int |
150 |
上方极值阈值系数。 |
HighLevel1 |
int |
20 |
上方中间阈值系数。 |
LowLevel1 |
int |
-20 |
下方中间阈值系数。 |
LowLevel2 |
int |
-150 |
下方极值阈值系数。 |
SignalBar |
int |
1 |
读取颜色所用的历史偏移。 |
使用说明
- 建议选择同时提供价格与成交量数据的品种;若缺乏真实成交量,则使用蜡烛的 tick 数作为替代。
- 通过分别调整
PrimaryVolume 和 SecondaryVolume,可模拟原始 EA 中两个资金管理通道。
- 当需要避开未完成蜡烛或重新同步历史数据时,可修改
SignalBar。
- 平滑方法通过反射支持 Jurik 滤波器,以尽可能贴近 MQL
SmoothAlgorithms 库的行为。
- 只有在
StopLossPoints 或 TakeProfitPoints 为正时才会启动保护;将两者设为零可完全关闭自动止损/止盈。
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>
/// KlPrice-Vol strategy using EMA crossover with volume confirmation.
/// Buys on fast EMA crossing above slow EMA with rising volume.
/// Sells on reverse crossover.
/// </summary>
public class ExpIKlPriceVolStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public ExpIKlPriceVolStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 200)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// EMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 60;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 60;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_i_kl_price_vol_strategy(Strategy):
def __init__(self):
super(exp_i_kl_price_vol_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 50) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 200) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(exp_i_kl_price_vol_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(exp_i_kl_price_vol_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
# Check SL/TP
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
# EMA crossover
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 60
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return exp_i_kl_price_vol_strategy()