MSL EA 策略
概述
MSL EA 是一种突破策略,利用最近的局部极值构建动态支撑和阻力线。该算法捕捉短期分形高点和低点,根据设定的点距偏移,并在价格收盘突破这些水平时开仓。本版本由原始 MQL4 实现转换而来。
工作原理
- 监控每根 K 线的最高价和最低价以寻找局部极值。
- 在最近的 Level 个极值中选取最高值和最低值作为阻力线和支撑线。
- 每条线按 Distance 个最小跳动单位进行偏移,用于过滤噪音。
- 当收盘价突破上轨时买入;跌破下轨时卖出。
- 同时持仓数量受 Max Trades 限制。
参数
- Max Trades – 允许的最大持仓数量。
- Level – 计算水平所使用的局部极值数量。
- Distance – 从极值起算的偏移点数。
- Candle Type – 策略处理的 K 线周期。
说明
本 C# 版本采用 StockSharp 高级 API,并在代码中添加了英文注释。原 MQL4 辅助库中的风险管理功能被简化为基础的持仓检查。
using System;
using System.Linq;
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>
/// MSL EA strategy.
/// Builds support and resistance lines from local extremes and trades breakouts.
/// </summary>
public class MsleaStrategy : Strategy
{
private readonly StrategyParam<int> _maxTrades;
private readonly StrategyParam<int> _level;
private readonly StrategyParam<int> _distance;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highLevels = new();
private readonly List<decimal> _lowLevels = new();
private decimal? _prevHigh1;
private decimal? _prevHigh2;
private decimal? _prevLow1;
private decimal? _prevLow2;
private decimal? _msh;
private decimal? _msl;
/// <summary>
/// Maximum allowed open trades.
/// </summary>
public int MaxTrades
{
get => _maxTrades.Value;
set => _maxTrades.Value = value;
}
/// <summary>
/// Number of local extremes used to build levels.
/// </summary>
public int Level
{
get => _level.Value;
set => _level.Value = value;
}
/// <summary>
/// Distance from extremes in ticks.
/// </summary>
public int Distance
{
get => _distance.Value;
set => _distance.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize Mslea strategy.
/// </summary>
public MsleaStrategy()
{
_maxTrades = Param(nameof(MaxTrades), 2)
.SetGreaterThanZero()
.SetDisplay("Max Trades", "Maximum simultaneous trades", "General");
_level = Param(nameof(Level), 1)
.SetGreaterThanZero()
.SetDisplay("Level", "Number of extremes to look back", "General");
_distance = Param(nameof(Distance), 4)
.SetGreaterThanZero()
.SetDisplay("Distance", "Offset from extreme in ticks", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highLevels.Clear();
_lowLevels.Clear();
_prevHigh1 = _prevHigh2 = _prevLow1 = _prevLow2 = null;
_msh = _msl = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevHigh2 is decimal h2 && _prevHigh1 is decimal h1 && h2 < h1 && h1 > candle.HighPrice)
AddHigh(h1);
if (_prevLow2 is decimal l2 && _prevLow1 is decimal l1 && l2 > l1 && l1 < candle.LowPrice)
AddLow(l1);
_prevHigh2 = _prevHigh1;
_prevHigh1 = candle.HighPrice;
_prevLow2 = _prevLow1;
_prevLow1 = candle.LowPrice;
if (_msh is decimal top && _msl is decimal bottom)
{
var step = Security.PriceStep ?? 0.01m;
var offset = step * Distance;
var upper = top + offset;
var lower = bottom - offset;
if (candle.ClosePrice > upper && Position <= 0)
BuyMarket();
else if (candle.ClosePrice < lower && Position >= 0)
SellMarket();
}
}
private void AddHigh(decimal high)
{
_highLevels.Insert(0, high);
TrimList(_highLevels);
_msh = GetMax(_highLevels);
}
private void AddLow(decimal low)
{
_lowLevels.Insert(0, low);
TrimList(_lowLevels);
_msl = GetMin(_lowLevels);
}
private void TrimList(List<decimal> list)
{
while (list.Count > Level)
list.RemoveAt(list.Count - 1);
}
private static decimal GetMax(List<decimal> list)
{
var max = list[0];
for (var i = 1; i < list.Count; i++)
{
var v = list[i];
if (v > max)
max = v;
}
return max;
}
private static decimal GetMin(List<decimal> list)
{
var min = list[0];
for (var i = 1; i < list.Count; i++)
{
var v = list[i];
if (v < min)
min = v;
}
return min;
}
}
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.Strategies import Strategy
class mslea_strategy(Strategy):
def __init__(self):
super(mslea_strategy, self).__init__()
self._max_trades = self.Param("MaxTrades", 2) \
.SetDisplay("Max Trades", "Maximum simultaneous trades", "General")
self._level = self.Param("Level", 1) \
.SetDisplay("Level", "Number of extremes to look back", "General")
self._distance = self.Param("Distance", 4) \
.SetDisplay("Distance", "Offset from extreme in ticks", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._high_levels = []
self._low_levels = []
self._prev_high1 = None
self._prev_high2 = None
self._prev_low1 = None
self._prev_low2 = None
self._msh = None
self._msl = None
@property
def max_trades(self):
return self._max_trades.Value
@property
def level(self):
return self._level.Value
@property
def distance(self):
return self._distance.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(mslea_strategy, self).OnReseted()
self._high_levels = []
self._low_levels = []
self._prev_high1 = None
self._prev_high2 = None
self._prev_low1 = None
self._prev_low2 = None
self._msh = None
self._msl = None
def OnStarted2(self, time):
super(mslea_strategy, self).OnStarted2(time)
self._high_levels = []
self._low_levels = []
self._prev_high1 = None
self._prev_high2 = None
self._prev_low1 = None
self._prev_low2 = None
self._msh = None
self._msl = None
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self._prev_high2 is not None and self._prev_high1 is not None:
if self._prev_high2 < self._prev_high1 and self._prev_high1 > high:
self._add_high(self._prev_high1)
if self._prev_low2 is not None and self._prev_low1 is not None:
if self._prev_low2 > self._prev_low1 and self._prev_low1 < low:
self._add_low(self._prev_low1)
self._prev_high2 = self._prev_high1
self._prev_high1 = high
self._prev_low2 = self._prev_low1
self._prev_low1 = low
if self._msh is not None and self._msl is not None:
step = 0.01
offset = step * self.distance
upper = self._msh + offset
lower = self._msl - offset
close = float(candle.ClosePrice)
if close > upper and self.Position <= 0:
self.BuyMarket()
elif close < lower and self.Position >= 0:
self.SellMarket()
def _add_high(self, high):
self._high_levels.insert(0, high)
while len(self._high_levels) > self.level:
self._high_levels.pop()
self._msh = max(self._high_levels)
def _add_low(self, low):
self._low_levels.insert(0, low)
while len(self._low_levels) > self.level:
self._low_levels.pop()
self._msl = min(self._low_levels)
def CreateClone(self):
return mslea_strategy()