Starter V6 Mod E
Starter V6 Mod E 是将 MetaTrader 4 专家顾问 Starter_v6mod_e_www_forex-instruments_info.mq4 移植到 StockSharp 高阶 API 的版本。该策略保留了原始系统的 Laguerre 极值判定、双 EMA 动量过滤、CCI 过滤以及 EMA 角度门限逻辑,同时按照 StockSharp 事件驱动模型实现交易执行。
交易逻辑
- 趋势闸门:通过 34 周期 EMA 在可配置的起始/结束位移之间计算斜率,并将结果转换为点值。斜率为正时仅允许做多,斜率为负时仅允许做空,斜率近零则禁止新仓。
- Laguerre 极值:根据原始递推公式实现的 Laguerre RSI(默认 γ = 0.7),输出范围 0–1。做多要求当前值与上一根 K 线的值均低于
Laguerre Oversold,做空要求两者均高于Laguerre Overbought。 - EMA 动量过滤:基于 PRICE_MEDIAN 的 120 与 40 周期 EMA 必须同时上升方可做多,同时下降方可做空。
- CCI 确认:14 周期 CCI 需低于
-CCI Threshold才能做多,高于+CCI Threshold才能做空,对应 MQL 脚本中的Alpha过滤器。 - 周五保护:超过
Friday Block Hour后禁止开新仓,达到Friday Exit Hour时强制平掉所有持仓,以规避周末风险。
风险管理
- 可配置的止损、止盈及移动止损距离(单位:点),复刻原策略的资金管理行为。
- 移动止损会跟踪持仓以来的最大有利价格,一旦回撤超过设定值即平仓。
- 所有平仓操作均通过高阶 API 的
SellMarket/BuyMarket完成,符合平台要求。
参数
| 参数 | 说明 |
|---|---|
Volume |
每次市价开仓的交易量。 |
StopLossPips |
止损距离(点)。 |
TakeProfitPips |
止盈距离(点)。 |
TrailingStopPips |
移动止损距离(点,0 表示关闭)。 |
SlowEmaPeriod |
慢速 EMA 的周期(使用 PRICE_MEDIAN)。 |
FastEmaPeriod |
快速 EMA 的周期(使用 PRICE_MEDIAN)。 |
AngleEmaPeriod |
用于角度检测的 EMA 周期。 |
AngleStartShift / AngleEndShift |
计算 EMA 斜率时使用的起止位移。 |
AngleThreshold |
允许交易所需的最小斜率(点值)。 |
CciPeriod / CciThreshold |
CCI 周期及绝对阈值。 |
LaguerreGamma |
Laguerre 振荡器的 γ 参数。 |
LaguerreOversold / LaguerreOverbought |
Laguerre 0–1 区间的超卖/超买阈值。 |
CandleType |
使用的 K 线类型(默认 1 分钟)。 |
FridayBlockHour / FridayExitHour |
控制周五风险限制的小时数(本地时间)。 |
转换说明
- Laguerre 振荡器直接采用原始递推公式实现,保持 0–1 输出范围与 γ 平滑特性。
- EMA 斜率通过历史 EMA 值的点值差异代替 MQL 中的角度计算函数,实现同样的趋势门限。
- 原始脚本中的资金曲线/网格控制在该版本中未启用,保持与提供的 MT4 设置一致,同时符合 StockSharp 对显式仓位管理的推荐。
- 通过
OnNewMyTrade追踪成交价,用于计算移动止损与持仓最高/最低价。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Starter V6 Mod E strategy using dual EMA crossover with Laguerre RSI filter.
/// Buy when fast EMA crosses above slow EMA and Laguerre is oversold.
/// Sell when fast EMA crosses below slow EMA and Laguerre is overbought.
/// </summary>
public class StarterV6ModEStrategy : Strategy
{
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<decimal> _laguerreGamma;
private readonly StrategyParam<decimal> _laguerreOversold;
private readonly StrategyParam<decimal> _laguerreOverbought;
private readonly StrategyParam<DataType> _candleType;
private decimal _lagL0;
private decimal _lagL1;
private decimal _lagL2;
private decimal _lagL3;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _prevLaguerre;
private bool _hasPrev;
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
public decimal LaguerreGamma
{
get => _laguerreGamma.Value;
set => _laguerreGamma.Value = value;
}
public decimal LaguerreOversold
{
get => _laguerreOversold.Value;
set => _laguerreOversold.Value = value;
}
public decimal LaguerreOverbought
{
get => _laguerreOverbought.Value;
set => _laguerreOverbought.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public StarterV6ModEStrategy()
{
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 12)
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_laguerreGamma = Param(nameof(LaguerreGamma), 0.7m)
.SetDisplay("Laguerre Gamma", "Smoothing factor for Laguerre RSI", "Indicators");
_laguerreOversold = Param(nameof(LaguerreOversold), 0.5m)
.SetDisplay("Laguerre Oversold", "Oversold level (0-1)", "Indicators");
_laguerreOverbought = Param(nameof(LaguerreOverbought), 0.5m)
.SetDisplay("Laguerre Overbought", "Overbought level (0-1)", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lagL0 = 0m;
_lagL1 = 0m;
_lagL2 = 0m;
_lagL3 = 0m;
_prevFast = 0m;
_prevSlow = 0m;
_prevLaguerre = 0m;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
_lagL0 = _lagL1 = _lagL2 = _lagL3 = 0m;
var fastEma = new ExponentialMovingAverage { Length = FastEmaPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
var laguerre = CalculateLaguerre(candle.ClosePrice);
if (!_hasPrev)
{
_prevFast = fast;
_prevSlow = slow;
_prevLaguerre = laguerre;
_hasPrev = true;
return;
}
// EMA crossover signals
var bullishCross = _prevFast <= _prevSlow && fast > slow;
var bearishCross = _prevFast >= _prevSlow && fast < slow;
// Long: fast EMA crosses above slow + Laguerre was oversold
if (Position <= 0 && bullishCross && _prevLaguerre <= LaguerreOversold)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Short: fast EMA crosses below slow + Laguerre was overbought
else if (Position >= 0 && bearishCross && _prevLaguerre >= LaguerreOverbought)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevFast = fast;
_prevSlow = slow;
_prevLaguerre = laguerre;
}
private decimal CalculateLaguerre(decimal price)
{
var gamma = LaguerreGamma;
var l0Prev = _lagL0;
var l1Prev = _lagL1;
var l2Prev = _lagL2;
var l3Prev = _lagL3;
_lagL0 = (1m - gamma) * price + gamma * l0Prev;
_lagL1 = -gamma * _lagL0 + l0Prev + gamma * l1Prev;
_lagL2 = -gamma * _lagL1 + l1Prev + gamma * l2Prev;
_lagL3 = -gamma * _lagL2 + l2Prev + gamma * l3Prev;
decimal cu = 0m;
decimal cd = 0m;
if (_lagL0 >= _lagL1) cu = _lagL0 - _lagL1; else cd = _lagL1 - _lagL0;
if (_lagL1 >= _lagL2) cu += _lagL1 - _lagL2; else cd += _lagL2 - _lagL1;
if (_lagL2 >= _lagL3) cu += _lagL2 - _lagL3; else cd += _lagL3 - _lagL2;
var denom = cu + cd;
return denom == 0m ? 0m : cu / denom;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class starter_v6_mod_e_strategy(Strategy):
"""Dual EMA crossover strategy with Laguerre RSI filter.
Buy when fast EMA crosses above slow EMA and Laguerre was oversold.
Sell when fast EMA crosses below slow EMA and Laguerre was overbought."""
def __init__(self):
super(starter_v6_mod_e_strategy, self).__init__()
self._slow_ema_period = self.Param("SlowEmaPeriod", 26) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._fast_ema_period = self.Param("FastEmaPeriod", 12) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._laguerre_gamma = self.Param("LaguerreGamma", 0.7) \
.SetDisplay("Laguerre Gamma", "Smoothing factor for Laguerre RSI", "Indicators")
self._laguerre_oversold = self.Param("LaguerreOversold", 0.5) \
.SetDisplay("Laguerre Oversold", "Oversold level (0-1)", "Indicators")
self._laguerre_overbought = self.Param("LaguerreOverbought", 0.5) \
.SetDisplay("Laguerre Overbought", "Overbought level (0-1)", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._lag_l0 = 0.0
self._lag_l1 = 0.0
self._lag_l2 = 0.0
self._lag_l3 = 0.0
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_laguerre = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def SlowEmaPeriod(self):
return self._slow_ema_period.Value
@property
def FastEmaPeriod(self):
return self._fast_ema_period.Value
@property
def LaguerreGamma(self):
return self._laguerre_gamma.Value
@property
def LaguerreOversold(self):
return self._laguerre_oversold.Value
@property
def LaguerreOverbought(self):
return self._laguerre_overbought.Value
def OnReseted(self):
super(starter_v6_mod_e_strategy, self).OnReseted()
self._lag_l0 = 0.0
self._lag_l1 = 0.0
self._lag_l2 = 0.0
self._lag_l3 = 0.0
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_laguerre = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(starter_v6_mod_e_strategy, self).OnStarted2(time)
self._has_prev = False
self._lag_l0 = 0.0
self._lag_l1 = 0.0
self._lag_l2 = 0.0
self._lag_l3 = 0.0
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastEmaPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowEmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, self._process_candle).Start()
def _calculate_laguerre(self, price):
gamma = float(self.LaguerreGamma)
l0_prev = self._lag_l0
l1_prev = self._lag_l1
l2_prev = self._lag_l2
l3_prev = self._lag_l3
self._lag_l0 = (1.0 - gamma) * price + gamma * l0_prev
self._lag_l1 = -gamma * self._lag_l0 + l0_prev + gamma * l1_prev
self._lag_l2 = -gamma * self._lag_l1 + l1_prev + gamma * l2_prev
self._lag_l3 = -gamma * self._lag_l2 + l2_prev + gamma * l3_prev
cu = 0.0
cd = 0.0
if self._lag_l0 >= self._lag_l1:
cu = self._lag_l0 - self._lag_l1
else:
cd = self._lag_l1 - self._lag_l0
if self._lag_l1 >= self._lag_l2:
cu += self._lag_l1 - self._lag_l2
else:
cd += self._lag_l2 - self._lag_l1
if self._lag_l2 >= self._lag_l3:
cu += self._lag_l2 - self._lag_l3
else:
cd += self._lag_l3 - self._lag_l2
denom = cu + cd
if denom == 0.0:
return 0.0
return cu / denom
def _process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast)
slow_val = float(slow)
close = float(candle.ClosePrice)
laguerre = self._calculate_laguerre(close)
if not self._has_prev:
self._prev_fast = fast_val
self._prev_slow = slow_val
self._prev_laguerre = laguerre
self._has_prev = True
return
# EMA crossover signals
bullish_cross = self._prev_fast <= self._prev_slow and fast_val > slow_val
bearish_cross = self._prev_fast >= self._prev_slow and fast_val < slow_val
# Long: fast EMA crosses above slow + Laguerre was oversold
if self.Position <= 0 and bullish_cross and self._prev_laguerre <= float(self.LaguerreOversold):
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Short: fast EMA crosses below slow + Laguerre was overbought
elif self.Position >= 0 and bearish_cross and self._prev_laguerre >= float(self.LaguerreOverbought):
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
self._prev_laguerre = laguerre
def CreateClone(self):
return starter_v6_mod_e_strategy()