在 GitHub 上查看
MultiMartinStrategy
概述
MultiMartinStrategy 是 MQL5 专家顾问 MultiMartin 的 StockSharp 版本。原始策略针对多品种执行反转+马丁格尔逻辑:在出现亏损时放大下一次下单手数,并在每次交易后切换方向。本移植版本保留核心的资金管理思想,同时使用 StockSharp 的高级 API 完成委托下单、仓位监控、可选的移动止损以及券商拒单后的冷却处理。
策略始终在所选标的上保持至多一笔仓位。平仓后如果交易盈利,则保持相同方向并将手数重置为初始值;若交易亏损,则反向开仓并按马丁因子放大手数(不超过指定上限)。
交易逻辑
- 入场条件
- 通过时间过滤器限定可交易时段,超出时段不会尝试开仓。
- 当当前仓位为零且未处于冷却期时,根据当前方向发送市价单。首笔方向可配置为买入或卖出。
- 马丁加仓
- 每次亏损后,下一单的手数乘以
Factor 参数。
- 手数放大次数由
Limit 限制,超出上限后自动回落至基础手数 Volume。
- 盈利交易始终将手数重置为基础值,并保持原方向。
- 离场管理
- 止损与止盈距离以“点”为单位配置,并结合合约的
PriceStep 转换为绝对价格距离。
- 可选的移动止损模式支持“保本”以及“直线跟随”两种方案。
- 当蜡烛的最高/最低突破目标价格时,策略会通过市价单平仓。
- 拒单冷却
- 如果券商拒绝了市价单,策略会根据
SkipBadTime 进入冷却期,在此期间不再尝试新开仓。Forever 选项会让策略在本次运行中不再交易。
参数
| 参数 |
说明 |
UseTimeFilter |
是否启用时间过滤器。 |
HourStart |
允许交易的起始小时(0-23,含)。 |
HourEnd |
允许交易的结束小时(1-24,排除)。支持跨夜区间。 |
Volume |
基础手数(手或合约数量)。 |
Factor |
亏损后用于放大下一单手数的倍数。 |
Limit |
连续放大手数的最大次数,超过后恢复基础手数。 |
StopLossPoints |
止损距离(点)。为 0 时关闭止损。 |
TakeProfitPoints |
止盈距离(点)。为 0 时关闭止盈。 |
StartDirection |
首笔方向(Buy 或 Sell)。 |
SkipBadTime |
拒单后的冷却时长,Forever 表示禁用后续交易。 |
TrailMode |
移动止损模式:None、Breakeven 或 Straight。 |
CandleType |
用于离场判断与时间过滤的数据序列。 |
与原版的差异
- 本版本每个实例仅交易一个标的,如需多品种请启动多个策略实例。
- 止损/止盈触发采用蜡烛高低点判断,一旦触达即通过市价单平仓。
- 拒单处理使用 StockSharp 的
OnOrderFailed 回调,实现与原始脚本类似的冷却逻辑。
- 移动止损通过策略级逻辑实现,而非直接修改挂单。
使用建议
- 启动前请正确设置
Security 与 Portfolio。
- 确认
Volume 符合交易品种的最小手数及步长限制。
- 将
StopLossPoints 或 TakeProfitPoints 设为 0 可关闭对应的保护单。
- 回测时请选择与历史数据匹配的
CandleType(例如外汇常用的 1 分钟)。
- 若要复刻原策略的多品种行为,可为不同交易品种分别运行策略实例。
风险提示
马丁格尔资金管理风险极高,连续亏损会快速放大持仓并耗尽保证金。请谨慎设置手数,先进行充分的历史测试,并在真实交易前施加严格的风险控制。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Multi Martin reversal strategy: alternates long/short based on EMA crossover.
/// Buys when EMA fast crosses above slow, sells on cross below.
/// </summary>
public class MultiMartinStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public MultiMartinStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
decimal? prevFast = null;
decimal? prevSlow = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, (candle, fastVal, slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (prevFast.HasValue && prevSlow.HasValue)
{
if (prevFast.Value <= prevSlow.Value && fastVal > slowVal && Position <= 0)
BuyMarket();
else if (prevFast.Value >= prevSlow.Value && fastVal < slowVal && Position >= 0)
SellMarket();
}
prevFast = fastVal;
prevSlow = slowVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
}
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 multi_martin_strategy(Strategy):
def __init__(self):
super(multi_martin_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._fast_period = self.Param("FastPeriod", 8) \
.SetGreaterThanZero() \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21) \
.SetGreaterThanZero() \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(multi_martin_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(multi_martin_strategy, self).OnStarted2(time)
self._fast_ind = ExponentialMovingAverage()
self._fast_ind.Length = self._fast_period.Value
self._slow_ind = ExponentialMovingAverage()
self._slow_ind.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast_ind, self._slow_ind, self._process_candle).Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if self._prev_fast is not None and self._prev_slow is not None:
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return multi_martin_strategy()