Starter V6 Mod 策略(StockSharp 版本)
概述
Starter V6 Mod 是将 MetaTrader 5 专家顾问 Starter_v6mod 迁移到 StockSharp 高阶 API 的结果。原系统以 Laguerre RSI、双指数均线、CCI 与网格式仓位控制为核心。本转换在 StockSharp 平台中复刻了多层过滤、分批加仓、动态仓位与保护逻辑。
交易逻辑
指标组合
- Laguerre RSI 代理:使用 14 周期 RSI 并标准化到 0-1,模拟原 Laguerre RSI,参数
LevelDown与LevelUp定义超卖/超买。 - 慢速 EMA(120)与快速 EMA(40):基于蜡烛的中位价计算,两者的价差用于判断趋势方向,
AngleThreshold以最小变动单位衡量价差阈值。 - CCI(14):确认动量方向,做多需 CCI < 0,做空需 CCI > 0。
入场条件
- 依据 EMA 价差确定趋势方向:
慢 EMA - 快 EMA < -AngleThreshold时仅允许做多;慢 EMA - 快 EMA > AngleThreshold时仅允许做空;- 介于阈值内视为震荡,不开新仓。
- 在趋势方向允许的情况下,需同时满足振荡器与动量过滤:
- 多头:Laguerre 代理 <
LevelDown,慢 EMA、快 EMA 均低于其前值,且 CCI < 0; - 空头:Laguerre 代理 >
LevelUp,慢 EMA、快 EMA 均高于其前值,且 CCI > 0。
- 多头:Laguerre 代理 <
- 网格加仓:若已有同向仓位,当前价需低于所有多单最低价
GridStepPips(或高于所有空单最高价)方可加仓。 - 仓位数量:同向网格持仓数不能超过
MaxOpenTrades。
离场条件
- Laguerre 反向信号:多头在指标上穿
LevelUp时平仓,空头在下穿LevelDown时平仓。 - 止损/止盈:以点值设置,按品种的最小跳动转换成价格差,兼容 3/5 位点差品种。
- 追踪止损:当浮盈超过
TrailingStopPips + TrailingStepPips后开始跟随,偏移量为TrailingStopPips。 - 周五保护:18:00 后不再开仓,20:00 强制平仓。
资金管理
- 仓位大小:可固定(
UseManualVolume=true)或按风险计算,风险模式下Volume = Equity * RiskPercent / StopLoss距离。 - 权益阈值:权益低于
EquityCutoff时停止开新仓。 - 日内亏损限制:同一自然日内亏损平仓次数达到
MaxLossesPerDay后停止交易。 - 递减加仓:每次亏损后将下一笔仓位除以
DecreaseFactor^亏损次数。
实现说明
- 使用高阶 API
SubscribeCandles().Bind(...)绑定蜡烛及指标数据,保证仅在完整蜡烛上决策。 - 因缺乏原 Laguerre RSI 实现,采用标准 RSI 映射至 0-1 区间作为替代,阈值保持一致。
- EMA 角度过滤通过比较快慢 EMA 价差与
AngleThreshold(以 tick 表示)来实现,与原emaangle自定义指标效果对应。 - 止损、追踪逻辑在
ProcessCandle中手动更新,以匹配 MQL 中对持仓保护的逐笔修改。 - 通过记录平均持仓价、最低/最高持仓价与追踪阈值,在 StockSharp 聚合仓位模型下复现网格行为。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
UseManualVolume |
false |
是否使用固定仓位。 |
ManualVolume |
1 |
手动模式下的开仓量。 |
RiskPercent |
5 |
风险百分比(自动仓位模式)。 |
StopLossPips |
35 |
止损点数。 |
TakeProfitPips |
10 |
止盈点数。 |
TrailingStopPips |
0 |
追踪止损点数(0 表示关闭)。 |
TrailingStepPips |
5 |
启动追踪前需达到的额外点数。 |
DecreaseFactor |
1.6 |
亏损后缩减仓位的系数。 |
MaxLossesPerDay |
3 |
每日允许的最大亏损次数。 |
EquityCutoff |
800 |
停止交易的权益下限。 |
MaxOpenTrades |
10 |
同向最大网格仓位数。 |
GridStepPips |
30 |
网格加仓所需的最小价差。 |
LongEmaPeriod |
120 |
慢 EMA 周期。 |
ShortEmaPeriod |
40 |
快 EMA 周期。 |
CciPeriod |
14 |
CCI 周期。 |
AngleThreshold |
3 |
EMA 价差阈值(tick)。 |
LevelUp |
0.85 |
Laguerre 上界。 |
LevelDown |
0.15 |
Laguerre 下界。 |
CandleType |
15m |
使用的蜡烛周期。 |
使用建议
- 将
CandleType设置为与原 MT5 参数一致的时间框架(EA 常用于 15 分钟)。 - 采用风险仓位模式时,应根据品种波动调整
StopLossPips,避免仓位异常。 - 确认交易所时间,内置的周五平仓逻辑以服务器时间为准。
- 建议开启图表绘制以观察 EMA、RSI 代理、CCI 与成交点,便于调试与优化。
- 若从 MT5 迁移参数,需要注意 RSI 代理与 Laguerre RSI 仍有细微差异,可微调阈值。
文件
CS/StarterV6ModStrategy.cs– 策略主代码。README.md– 英文说明。README_zh.md– 中文说明(当前文件)。README_ru.md– 俄文说明。
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>
/// Conversion of the Starter_v6mod Expert Advisor using the high-level StockSharp API.
/// </summary>
public class StarterV6ModStrategy : Strategy
{
private readonly StrategyParam<bool> _useManualVolume;
private readonly StrategyParam<decimal> _manualVolume;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<decimal> _decreaseFactor;
private readonly StrategyParam<int> _maxLossesPerDay;
private readonly StrategyParam<decimal> _equityCutoff;
private readonly StrategyParam<int> _maxOpenTrades;
private readonly StrategyParam<int> _gridStepPips;
private readonly StrategyParam<int> _longEmaPeriod;
private readonly StrategyParam<int> _shortEmaPeriod;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _angleThreshold;
private readonly StrategyParam<decimal> _levelUp;
private readonly StrategyParam<decimal> _levelDown;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _longEma;
private ExponentialMovingAverage _shortEma;
private CommodityChannelIndex _cci;
private RelativeStrengthIndex _laguerreProxy;
private decimal? _prevLongEma;
private decimal? _prevShortEma;
/// <summary>
/// Use manual volume instead of risk calculation.
/// </summary>
public bool UseManualVolume
{
get => _useManualVolume.Value;
set => _useManualVolume.Value = value;
}
/// <summary>
/// Manual volume for each new entry.
/// </summary>
public decimal ManualVolume
{
get => _manualVolume.Value;
set => _manualVolume.Value = value;
}
/// <summary>
/// Risk percentage used when position sizing is automatic.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance expressed in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Additional distance required before the trailing stop starts to follow the price.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Multiplier used to reduce the position size after losses.
/// </summary>
public decimal DecreaseFactor
{
get => _decreaseFactor.Value;
set => _decreaseFactor.Value = value;
}
/// <summary>
/// Maximum number of losing trades allowed per day.
/// </summary>
public int MaxLossesPerDay
{
get => _maxLossesPerDay.Value;
set => _maxLossesPerDay.Value = value;
}
/// <summary>
/// Equity threshold below which the strategy stops opening new trades.
/// </summary>
public decimal EquityCutoff
{
get => _equityCutoff.Value;
set => _equityCutoff.Value = value;
}
/// <summary>
/// Maximum number of simultaneously opened grid positions.
/// </summary>
public int MaxOpenTrades
{
get => _maxOpenTrades.Value;
set => _maxOpenTrades.Value = value;
}
/// <summary>
/// Grid step in pips used when stacking positions.
/// </summary>
public int GridStepPips
{
get => _gridStepPips.Value;
set => _gridStepPips.Value = value;
}
/// <summary>
/// Period for the slow EMA trend filter.
/// </summary>
public int LongEmaPeriod
{
get => _longEmaPeriod.Value;
set => _longEmaPeriod.Value = value;
}
/// <summary>
/// Period for the fast EMA trend filter.
/// </summary>
public int ShortEmaPeriod
{
get => _shortEmaPeriod.Value;
set => _shortEmaPeriod.Value = value;
}
/// <summary>
/// Period for the CCI momentum filter.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// Threshold in ticks for the EMA spread trend detector.
/// </summary>
public decimal AngleThreshold
{
get => _angleThreshold.Value;
set => _angleThreshold.Value = value;
}
/// <summary>
/// Upper Laguerre RSI level.
/// </summary>
public decimal LevelUp
{
get => _levelUp.Value;
set => _levelUp.Value = value;
}
/// <summary>
/// Lower Laguerre RSI level.
/// </summary>
public decimal LevelDown
{
get => _levelDown.Value;
set => _levelDown.Value = value;
}
/// <summary>
/// Candle data type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="StarterV6ModStrategy"/> class.
/// </summary>
public StarterV6ModStrategy()
{
_useManualVolume = Param(nameof(UseManualVolume), true)
.SetDisplay("Manual Volume", "Use manual volume instead of risk-based sizing", "Money Management");
_manualVolume = Param(nameof(ManualVolume), 1m)
.SetRange(0.01m, 100m)
.SetDisplay("Volume", "Manual volume per trade", "Money Management")
;
_riskPercent = Param(nameof(RiskPercent), 5m)
.SetRange(0.5m, 20m)
.SetDisplay("Risk %", "Risk percentage when auto-sizing trades", "Money Management")
;
_stopLossPips = Param(nameof(StopLossPips), 35)
.SetRange(0, 500)
.SetDisplay("Stop Loss", "Stop-loss distance in pips", "Risk Management");
_takeProfitPips = Param(nameof(TakeProfitPips), 10)
.SetRange(0, 500)
.SetDisplay("Take Profit", "Take-profit distance in pips", "Risk Management");
_trailingStopPips = Param(nameof(TrailingStopPips), 0)
.SetRange(0, 500)
.SetDisplay("Trailing Stop", "Trailing stop distance in pips", "Risk Management");
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetRange(0, 500)
.SetDisplay("Trailing Step", "Additional distance before trailing activates", "Risk Management");
_decreaseFactor = Param(nameof(DecreaseFactor), 1.6m)
.SetRange(1m, 10m)
.SetDisplay("Decrease Factor", "Volume reduction factor after losses", "Money Management");
_maxLossesPerDay = Param(nameof(MaxLossesPerDay), 3)
.SetRange(0, 20)
.SetDisplay("Daily Loss Limit", "Maximum number of losses per day", "Risk Management");
_equityCutoff = Param(nameof(EquityCutoff), 800m)
.SetRange(0m, 1_000_000m)
.SetDisplay("Equity Cutoff", "Stop trading if equity drops below this value", "Risk Management");
_maxOpenTrades = Param(nameof(MaxOpenTrades), 10)
.SetRange(1, 100)
.SetDisplay("Max Trades", "Maximum simultaneous grid positions", "General");
_gridStepPips = Param(nameof(GridStepPips), 30)
.SetRange(0, 500)
.SetDisplay("Grid Step", "Minimum pip distance between stacked entries", "General");
_longEmaPeriod = Param(nameof(LongEmaPeriod), 120)
.SetRange(10, 400)
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
;
_shortEmaPeriod = Param(nameof(ShortEmaPeriod), 40)
.SetRange(5, 200)
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
;
_cciPeriod = Param(nameof(CciPeriod), 14)
.SetRange(5, 100)
.SetDisplay("CCI Period", "CCI indicator length", "Indicators")
;
_angleThreshold = Param(nameof(AngleThreshold), 3m)
.SetRange(0m, 50m)
.SetDisplay("Angle Threshold", "EMA spread threshold measured in ticks", "Indicators");
_levelUp = Param(nameof(LevelUp), 0.85m)
.SetRange(0.1m, 1m)
.SetDisplay("Laguerre Up", "Upper Laguerre RSI level", "Indicators");
_levelDown = Param(nameof(LevelDown), 0.15m)
.SetRange(0m, 0.9m)
.SetDisplay("Laguerre Down", "Lower Laguerre RSI level", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for analysis", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_longEma = null;
_shortEma = null;
_cci = null;
_laguerreProxy = null;
_prevLongEma = null;
_prevShortEma = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_longEma = new ExponentialMovingAverage { Length = LongEmaPeriod };
_shortEma = new ExponentialMovingAverage { Length = ShortEmaPeriod };
_cci = new CommodityChannelIndex { Length = CciPeriod };
_laguerreProxy = new RelativeStrengthIndex { Length = 14 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_longEma, _shortEma, _cci, _laguerreProxy, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _longEma);
DrawIndicator(area, _shortEma);
DrawIndicator(area, _cci);
DrawIndicator(area, _laguerreProxy);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal longEmaValue, decimal shortEmaValue, decimal cciValue, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevLongEma is null || _prevShortEma is null)
{
_prevLongEma = longEmaValue;
_prevShortEma = shortEmaValue;
return;
}
if (Position != 0)
{
_prevLongEma = longEmaValue;
_prevShortEma = shortEmaValue;
return;
}
var laguerre = rsiValue / 100m;
// Buy: RSI low (oversold), EMAs falling (pullback), CCI negative
var buySignal = laguerre < LevelDown && cciValue < 0m;
// Sell: RSI high (overbought), EMAs rising, CCI positive
var sellSignal = laguerre > LevelUp && cciValue > 0m;
if (buySignal)
BuyMarket();
else if (sellSignal)
SellMarket();
_prevLongEma = longEmaValue;
_prevShortEma = shortEmaValue;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import (
ExponentialMovingAverage, CommodityChannelIndex, RelativeStrengthIndex
)
from StockSharp.Algo.Strategies import Strategy
class starter_v6_mod_strategy(Strategy):
def __init__(self):
super(starter_v6_mod_strategy, self).__init__()
self._use_manual_volume = self.Param("UseManualVolume", True)
self._manual_volume = self.Param("ManualVolume", 1.0)
self._risk_percent = self.Param("RiskPercent", 5.0)
self._stop_loss_pips = self.Param("StopLossPips", 35)
self._take_profit_pips = self.Param("TakeProfitPips", 10)
self._trailing_stop_pips = self.Param("TrailingStopPips", 0)
self._trailing_step_pips = self.Param("TrailingStepPips", 5)
self._decrease_factor = self.Param("DecreaseFactor", 1.6)
self._max_losses_per_day = self.Param("MaxLossesPerDay", 3)
self._equity_cutoff = self.Param("EquityCutoff", 800.0)
self._max_open_trades = self.Param("MaxOpenTrades", 10)
self._grid_step_pips = self.Param("GridStepPips", 30)
self._long_ema_period = self.Param("LongEmaPeriod", 120)
self._short_ema_period = self.Param("ShortEmaPeriod", 40)
self._cci_period = self.Param("CciPeriod", 14)
self._angle_threshold = self.Param("AngleThreshold", 3.0)
self._level_up = self.Param("LevelUp", 0.85)
self._level_down = self.Param("LevelDown", 0.15)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._long_ema = None
self._short_ema = None
self._cci = None
self._laguerre_proxy = None
self._prev_long_ema = None
self._prev_short_ema = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def UseManualVolume(self):
return self._use_manual_volume.Value
@property
def ManualVolume(self):
return self._manual_volume.Value
@property
def RiskPercent(self):
return self._risk_percent.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def TrailingStopPips(self):
return self._trailing_stop_pips.Value
@property
def TrailingStepPips(self):
return self._trailing_step_pips.Value
@property
def DecreaseFactor(self):
return self._decrease_factor.Value
@property
def MaxLossesPerDay(self):
return self._max_losses_per_day.Value
@property
def EquityCutoff(self):
return self._equity_cutoff.Value
@property
def MaxOpenTrades(self):
return self._max_open_trades.Value
@property
def GridStepPips(self):
return self._grid_step_pips.Value
@property
def LongEmaPeriod(self):
return self._long_ema_period.Value
@property
def ShortEmaPeriod(self):
return self._short_ema_period.Value
@property
def CciPeriod(self):
return self._cci_period.Value
@property
def AngleThreshold(self):
return self._angle_threshold.Value
@property
def LevelUp(self):
return self._level_up.Value
@property
def LevelDown(self):
return self._level_down.Value
def OnStarted2(self, time):
super(starter_v6_mod_strategy, self).OnStarted2(time)
self._long_ema = ExponentialMovingAverage()
self._long_ema.Length = self.LongEmaPeriod
self._short_ema = ExponentialMovingAverage()
self._short_ema.Length = self.ShortEmaPeriod
self._cci = CommodityChannelIndex()
self._cci.Length = self.CciPeriod
self._laguerre_proxy = RelativeStrengthIndex()
self._laguerre_proxy.Length = 14
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._long_ema, self._short_ema, self._cci, self._laguerre_proxy, self._process_candle).Start()
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(1, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._long_ema)
self.DrawIndicator(area, self._short_ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, long_ema_v, short_ema_v, cci_v, rsi_v):
if candle.State != CandleStates.Finished:
return
if self._prev_long_ema is None or self._prev_short_ema is None:
self._prev_long_ema = long_ema_v
self._prev_short_ema = short_ema_v
return
if self.Position != 0:
self._prev_long_ema = long_ema_v
self._prev_short_ema = short_ema_v
return
laguerre = rsi_v / 100.0
buy_signal = laguerre < self.LevelDown and cci_v < 0
sell_signal = laguerre > self.LevelUp and cci_v > 0
if buy_signal:
self.BuyMarket()
elif sell_signal:
self.SellMarket()
self._prev_long_ema = long_ema_v
self._prev_short_ema = short_ema_v
def OnReseted(self):
super(starter_v6_mod_strategy, self).OnReseted()
self._long_ema = None
self._short_ema = None
self._cci = None
self._laguerre_proxy = None
self._prev_long_ema = None
self._prev_short_ema = None
def CreateClone(self):
return starter_v6_mod_strategy()