在 GitHub 上查看

Martingail Expert 策略

概述

Martingail Expert 是一种基于随机指标 (Stochastic Oscillator) 的马丁格尔策略。指标给出方向后,策略会以动态盈利目标和几何级数的仓位扩张来管理一组连续的市价单。

交易逻辑

  • 在设定的K线序列上计算随机指标,并缓存最近一次收盘时的 %K 和 %D 数值。
  • 当上一根K线的 %K 大于 %D%D 高于 BuyLevel 阈值时,开启新的多头序列。
  • 当上一根K线的 %K 小于 %D%D 低于 SellLevel 阈值时,开启新的空头序列。
  • 进入序列后,价格每向有利方向移动 ProfitFactor × 持仓订单数 点时,会按基础手数再加仓一次。
  • 价格每向不利方向移动 StepPoints 点时,会将最近一次成交量乘以 Multiplier,并按相同方向再下市价单进行摊平。

离场规则

  • 当最新成交价朝有利方向达到 ProfitFactor × 持仓订单数 点的动态利润目标时,平掉全部仓位。
  • 当总仓位归零时,重置所有马丁格尔状态。

风险控制

马丁格尔会在行情逆向时迅速放大仓位压力。请根据账户规模与标的波动性谨慎调整 MultiplierStepPointsProfitFactor 参数。

参数

名称 说明
Volume 首笔以及顺势加仓所使用的基础手数。
Multiplier 逆势摊平时应用于上一笔成交量的放大倍数。
StepPoints 触发逆势摊平的点数距离。
ProfitFactor 每个持仓订单的目标利润点数,实际距离为 ProfitFactor × 持仓订单数
KPeriod %K 线的回溯周期。
DPeriod %D 线的平滑周期。
Slowing 应用于 %K 的额外平滑长度。
BuyLevel 允许开多序列的最小 %D 值。
SellLevel 允许开空序列的最大 %D 值。
CandleType 用于计算指标的K线类型(默认 5 分钟)。

备注

  • 更适合价差与成交量步进较细的外汇主要货币对。
  • 需要充足保证金以承受多次马丁格尔加仓。
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Martingale strategy driven by stochastic oscillator crossovers.
/// Buy when K crosses above D, sell when K crosses below D.
/// Doubles down on adverse moves with martingale averaging.
/// </summary>
public class MartingailExpertSequenceStrategy : Strategy
{
	private readonly StrategyParam<int> _kPeriod;
	private readonly StrategyParam<int> _dPeriod;
	private readonly StrategyParam<decimal> _stepPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevK;
	private decimal? _prevD;
	private decimal _entryPrice;

	public int KPeriod { get => _kPeriod.Value; set => _kPeriod.Value = value; }
	public int DPeriod { get => _dPeriod.Value; set => _dPeriod.Value = value; }
	public decimal StepPoints { get => _stepPoints.Value; set => _stepPoints.Value = value; }
	public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MartingailExpertSequenceStrategy()
	{
		_kPeriod = Param(nameof(KPeriod), 14)
			.SetDisplay("K Period", "Stochastic K period", "Indicators");

		_dPeriod = Param(nameof(DPeriod), 3)
			.SetDisplay("D Period", "Stochastic D period", "Indicators");

		_stepPoints = Param(nameof(StepPoints), 500m)
			.SetDisplay("Step Points", "Distance for martingale averaging", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
			.SetDisplay("Take Profit", "Take profit in price steps", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevK = null;
		_prevD = null;
		_entryPrice = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevK = null;
		_prevD = null;

		var stoch = new StochasticOscillator
		{
			K = { Length = KPeriod },
			D = { Length = DPeriod }
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(stoch, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!stochValue.IsFinal)
			return;

		if (stochValue is not StochasticOscillatorValue stoch)
			return;

		if (stoch.K is not decimal k || stoch.D is not decimal d)
			return;

		var step = Security?.PriceStep ?? 1m;
		if (step <= 0) step = 1m;

		// Check take profit
		if (Position > 0 && candle.ClosePrice >= _entryPrice + TakeProfitPoints * step)
		{
			SellMarket();
			_prevK = k;
			_prevD = d;
			return;
		}
		else if (Position < 0 && candle.ClosePrice <= _entryPrice - TakeProfitPoints * step)
		{
			BuyMarket();
			_prevK = k;
			_prevD = d;
			return;
		}

		if (_prevK is not decimal prevK || _prevD is not decimal prevD)
		{
			_prevK = k;
			_prevD = d;
			return;
		}

		// Stochastic crossover signals
		var bullCross = prevK <= prevD && k > d;
		var bearCross = prevK >= prevD && k < d;

		if (Position == 0)
		{
			if (bullCross)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
			}
			else if (bearCross)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
			}
		}
		else if (Position > 0 && bearCross)
		{
			SellMarket(); // Close long
			SellMarket(); // Open short
			_entryPrice = candle.ClosePrice;
		}
		else if (Position < 0 && bullCross)
		{
			BuyMarket(); // Close short
			BuyMarket(); // Open long
			_entryPrice = candle.ClosePrice;
		}

		_prevK = k;
		_prevD = d;
	}
}