在 GitHub 上查看

MultiMartinStrategy

概述

MultiMartinStrategy 是 MQL5 专家顾问 MultiMartin 的 StockSharp 版本。原始策略针对多品种执行反转+马丁格尔逻辑:在出现亏损时放大下一次下单手数,并在每次交易后切换方向。本移植版本保留核心的资金管理思想,同时使用 StockSharp 的高级 API 完成委托下单、仓位监控、可选的移动止损以及券商拒单后的冷却处理。

策略始终在所选标的上保持至多一笔仓位。平仓后如果交易盈利,则保持相同方向并将手数重置为初始值;若交易亏损,则反向开仓并按马丁因子放大手数(不超过指定上限)。

交易逻辑

  1. 入场条件
    • 通过时间过滤器限定可交易时段,超出时段不会尝试开仓。
    • 当当前仓位为零且未处于冷却期时,根据当前方向发送市价单。首笔方向可配置为买入或卖出。
  2. 马丁加仓
    • 每次亏损后,下一单的手数乘以 Factor 参数。
    • 手数放大次数由 Limit 限制,超出上限后自动回落至基础手数 Volume
    • 盈利交易始终将手数重置为基础值,并保持原方向。
  3. 离场管理
    • 止损与止盈距离以“点”为单位配置,并结合合约的 PriceStep 转换为绝对价格距离。
    • 可选的移动止损模式支持“保本”以及“直线跟随”两种方案。
    • 当蜡烛的最高/最低突破目标价格时,策略会通过市价单平仓。
  4. 拒单冷却
    • 如果券商拒绝了市价单,策略会根据 SkipBadTime 进入冷却期,在此期间不再尝试新开仓。Forever 选项会让策略在本次运行中不再交易。

参数

参数 说明
UseTimeFilter 是否启用时间过滤器。
HourStart 允许交易的起始小时(0-23,含)。
HourEnd 允许交易的结束小时(1-24,排除)。支持跨夜区间。
Volume 基础手数(手或合约数量)。
Factor 亏损后用于放大下一单手数的倍数。
Limit 连续放大手数的最大次数,超过后恢复基础手数。
StopLossPoints 止损距离(点)。为 0 时关闭止损。
TakeProfitPoints 止盈距离(点)。为 0 时关闭止盈。
StartDirection 首笔方向(BuySell)。
SkipBadTime 拒单后的冷却时长,Forever 表示禁用后续交易。
TrailMode 移动止损模式:NoneBreakevenStraight
CandleType 用于离场判断与时间过滤的数据序列。

与原版的差异

  • 本版本每个实例仅交易一个标的,如需多品种请启动多个策略实例。
  • 止损/止盈触发采用蜡烛高低点判断,一旦触达即通过市价单平仓。
  • 拒单处理使用 StockSharp 的 OnOrderFailed 回调,实现与原始脚本类似的冷却逻辑。
  • 移动止损通过策略级逻辑实现,而非直接修改挂单。

使用建议

  • 启动前请正确设置 SecurityPortfolio
  • 确认 Volume 符合交易品种的最小手数及步长限制。
  • StopLossPointsTakeProfitPoints 设为 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);
		}
	}
}