双均线四级策略
该策略等价于 MetaTrader 顾问 "2MA_4Level",使用 StockSharp 的高级 API 实现。策略基于两条平滑移动平均线(SMMA),以每根K线的中价 (High + Low) / 2 作为输入。系统不仅监控快慢均线的直接交叉,还检测上下两个区间的四个偏移阈值。只有在没有持仓时才会开仓,每笔交易都绑定固定点数的止损和止盈。
交易逻辑
- 对所选时间框架的K线计算快线和慢线SMMA(默认周期分别为50和130)。
- 在每根已完成的K线上比较当前与上一根K线的SMMA数值,确认交叉方向。
- 交叉判断包含五条基准线:
- 原始慢线;
- 慢线 +
MostTopLevel点; - 慢线 +
TopLevel点; - 慢线 −
LowermostLevel点; - 慢线 −
LowerLevel点。
- 当快线向上穿越任一基准线时(且当前为空仓)开多单;当快线向下穿越任一基准线时开空单。
- 通过
StartProtection函数,利用合约的最小价位变动 (Security.PriceStep) 自动附加止损和止盈。
策略不会加仓或反手,必须等待上一笔仓位被止盈或止损后才能开新单。
参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
FastPeriod |
50 | 快速SMMA的周期,必须小于 SlowPeriod。 |
SlowPeriod |
130 | 慢速SMMA的周期。 |
MostTopLevel |
500 | 最高的上方偏移(点数),用于最宽松的确认条件,必须大于 TopLevel。 |
TopLevel |
250 | 第二级上方偏移(点数)。 |
LowerLevel |
250 | 第二级下方偏移(点数),必须小于 LowermostLevel。 |
LowermostLevel |
500 | 最低的下方偏移(点数)。 |
TakeProfitPips |
55 | 止盈距离,单位为点。 |
StopLossPips |
260 | 止损距离,单位为点。 |
CandleType |
15 分钟 | 用于指标计算和信号生成的K线类型。 |
实现细节
- 使用中价作为指标输入,以匹配 MT5 中的
PRICE_MEDIAN设置。 - 仅对已收盘的K线进行运算和判断,避免了未完成K线带来的噪音。
StartProtection在启动时调用一次,此后每笔委托都会继承统一的止损/止盈距离。OnStarted中包含参数合法性检查(如FastPeriod >= SlowPeriod),若配置不正确会写入错误日志并立即停止策略。
使用建议
- 绑定的证券应当提供有效的
PriceStep,否则点值会回退为1,可能导致风险控制不准确。 - 原版策略要求 MT5 对冲账户;在 StockSharp 中同样只允许一个净头寸,避免出现同时持有多笔订单的情况。
FastPeriod与SlowPeriod已启用优化标记,可直接使用 StockSharp 优化器进行参数寻优。- 策略仅依赖止损和止盈退出,请根据标的波动性调整点差,防止过长的持仓时间或过早止损。
文件列表
CS/TwoMaFourLevelStrategy.cs—— 策略 C# 实现。README.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>
/// Two smoothed moving average crossover strategy with level offsets.
/// </summary>
public class TwoMaFourLevelStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _mostTopLevel;
private readonly StrategyParam<int> _topLevel;
private readonly StrategyParam<int> _lowerLevel;
private readonly StrategyParam<int> _lowermostLevel;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<DataType> _candleType;
private SmoothedMovingAverage _fastMa = null!;
private SmoothedMovingAverage _slowMa = null!;
private decimal? _prevFast;
private decimal? _prevSlow;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int MostTopLevel { get => _mostTopLevel.Value; set => _mostTopLevel.Value = value; }
public int TopLevel { get => _topLevel.Value; set => _topLevel.Value = value; }
public int LowerLevel { get => _lowerLevel.Value; set => _lowerLevel.Value = value; }
public int LowermostLevel { get => _lowermostLevel.Value; set => _lowermostLevel.Value = value; }
public int TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public TwoMaFourLevelStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Period of the fast smoothed MA", "Moving Averages")
.SetOptimize(20, 150, 5);
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Period of the slow smoothed MA", "Moving Averages")
.SetOptimize(60, 300, 5);
_mostTopLevel = Param(nameof(MostTopLevel), 2)
.SetGreaterThanZero()
.SetDisplay("Extreme Upper Level", "Highest positive offset in points", "Levels");
_topLevel = Param(nameof(TopLevel), 1)
.SetGreaterThanZero()
.SetDisplay("Upper Level", "Second positive offset in points", "Levels");
_lowerLevel = Param(nameof(LowerLevel), 1)
.SetGreaterThanZero()
.SetDisplay("Lower Level", "Second negative offset in points", "Levels");
_lowermostLevel = Param(nameof(LowermostLevel), 2)
.SetGreaterThanZero()
.SetDisplay("Extreme Lower Level", "Largest negative offset in points", "Levels");
_takeProfitPips = Param(nameof(TakeProfitPips), 500)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pips)", "Distance to take profit", "Risk");
_stopLossPips = Param(nameof(StopLossPips), 1000)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pips)", "Distance to stop loss", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Time frame for analysis", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (FastPeriod >= SlowPeriod)
{
this.LogError("FastPeriod must be less than SlowPeriod.");
Stop();
return;
}
if (MostTopLevel <= TopLevel)
{
this.LogError("MostTopLevel must be greater than TopLevel.");
Stop();
return;
}
if (LowerLevel >= LowermostLevel)
{
this.LogError("LowerLevel must be less than LowermostLevel.");
Stop();
return;
}
_fastMa = new SmoothedMovingAverage { Length = FastPeriod };
_slowMa = new SmoothedMovingAverage { Length = SlowPeriod };
var pip = Security?.PriceStep ?? 1m;
StartProtection(
takeProfit: new Unit(TakeProfitPips * pip, UnitTypes.Absolute),
stopLoss: new Unit(StopLossPips * pip, UnitTypes.Absolute));
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_fastMa, _slowMa, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastMa);
DrawIndicator(area, _slowMa);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevFast is null || _prevSlow is null)
{
_prevFast = fast;
_prevSlow = slow;
return;
}
var pip = Security?.PriceStep ?? 0.05m;
var signal = GetSignal(fast, slow, _prevFast.Value, _prevSlow.Value, pip);
if (signal > 0)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
else if (signal < 0)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
_prevFast = fast;
_prevSlow = slow;
}
private int GetSignal(decimal fast, decimal slow, decimal prevFast, decimal prevSlow, decimal pip)
{
if (IsCrossUp(prevFast, fast, prevSlow, slow, 0m) ||
IsCrossUp(prevFast, fast, prevSlow, slow, MostTopLevel * pip) ||
IsCrossUp(prevFast, fast, prevSlow, slow, TopLevel * pip) ||
IsCrossUp(prevFast, fast, prevSlow, slow, -LowermostLevel * pip) ||
IsCrossUp(prevFast, fast, prevSlow, slow, -LowerLevel * pip))
{
return 1;
}
if (IsCrossDown(prevFast, fast, prevSlow, slow, 0m) ||
IsCrossDown(prevFast, fast, prevSlow, slow, MostTopLevel * pip) ||
IsCrossDown(prevFast, fast, prevSlow, slow, TopLevel * pip) ||
IsCrossDown(prevFast, fast, prevSlow, slow, -LowermostLevel * pip) ||
IsCrossDown(prevFast, fast, prevSlow, slow, -LowerLevel * pip))
{
return -1;
}
return 0;
}
private static bool IsCrossUp(decimal prevFast, decimal fast, decimal prevSlow, decimal slow, decimal offset)
{
var prevSlowShifted = prevSlow + offset;
var slowShifted = slow + offset;
return prevFast <= prevSlowShifted && fast > slowShifted;
}
private static bool IsCrossDown(decimal prevFast, decimal fast, decimal prevSlow, decimal slow, decimal offset)
{
var prevSlowShifted = prevSlow + offset;
var slowShifted = slow + offset;
return prevFast >= prevSlowShifted && fast < slowShifted;
}
}
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 SmoothedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class two_ma_four_level_strategy(Strategy):
def __init__(self):
super(two_ma_four_level_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 10)
self._slow_period = self.Param("SlowPeriod", 30)
self._most_top = self.Param("MostTopLevel", 2)
self._top = self.Param("TopLevel", 1)
self._lower = self.Param("LowerLevel", 1)
self._lowermost = self.Param("LowermostLevel", 2)
self._tp = self.Param("TakeProfitPips", 500)
self._sl = self.Param("StopLossPips", 1000)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(two_ma_four_level_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(two_ma_four_level_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast_ma = SmoothedMovingAverage()
fast_ma.Length = int(self._fast_period.Value)
slow_ma = SmoothedMovingAverage()
slow_ma.Length = int(self._slow_period.Value)
sec = self.Security
pip = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
tp_pips = int(self._tp.Value)
sl_pips = int(self._sl.Value)
self.StartProtection(
Unit(tp_pips * pip, UnitTypes.Absolute),
Unit(sl_pips * pip, UnitTypes.Absolute))
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ma, slow_ma, self.OnProcess).Start()
def OnProcess(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
sec = self.Security
pip = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.05
pf = self._prev_fast
ps = self._prev_slow
most_top = int(self._most_top.Value) * pip
top = int(self._top.Value) * pip
lower = int(self._lower.Value) * pip
lowermost = int(self._lowermost.Value) * pip
buy_signal = (self._is_cross_up(pf, fv, ps, sv, 0) or
self._is_cross_up(pf, fv, ps, sv, most_top) or
self._is_cross_up(pf, fv, ps, sv, top) or
self._is_cross_up(pf, fv, ps, sv, -lowermost) or
self._is_cross_up(pf, fv, ps, sv, -lower))
sell_signal = (self._is_cross_down(pf, fv, ps, sv, 0) or
self._is_cross_down(pf, fv, ps, sv, most_top) or
self._is_cross_down(pf, fv, ps, sv, top) or
self._is_cross_down(pf, fv, ps, sv, -lowermost) or
self._is_cross_down(pf, fv, ps, sv, -lower))
if buy_signal:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif sell_signal:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def _is_cross_up(self, prev_fast, fast, prev_slow, slow, offset):
return prev_fast <= prev_slow + offset and fast > slow + offset
def _is_cross_down(self, prev_fast, fast, prev_slow, slow, offset):
return prev_fast >= prev_slow + offset and fast < slow + offset
def CreateClone(self):
return two_ma_four_level_strategy()