GitHub で見る

Exp Martin V2戦略

Exp Martin V2戦略は指数マーチンゲールアプローチを実装しています。常に1つのオープンポジションのみを保持し、各取引後に最後の取引の損益に基づいて次の方向とボリュームを決定します。

戦略は事前定義された注文タイプ(買いまたは売り)と開始ボリュームで始まります。固定のテイクプロフィットとストップロスがすべてのポジションに適用されます。取引が利益で閉じると、同じタイプと開始ボリュームで新しいポジションが開かれます。取引が損失で終わると、方向が逆転し、ボリュームは指定された係数で乗算されます。乗算は損失ごとに最大乗算回数に達するまで続き、その後ボリュームは開始値にリセットされます。

これにより、利益のある動きが発生した際に以前の損失を回収することを目指す、逆方向の取引のエスカレートシーケンスが作成されます。

詳細

  • エントリーロジック:
    • Start Type (0 - 買い、1 - 売り) に従って Start Volume で最初のポジションを開く。
    • 利益のある取引の後、同じ方向と開始ボリュームで繰り返す。
    • 損失のある取引の後、方向を逆転させ、Limit 回の乗算に達するまでボリュームを Factor で乗算する。
  • ロング/ショート: 現在のシーケンスに応じて両方。
  • エグジットロジック:
    • 価格が設定された Take Profit または Stop Loss レベルに達するとポジションが閉じられます。
  • ストップ: ポイント単位の固定ストップロスとテイクプロフィット。
  • フィルター: なし。
  • ポジション管理: 同時に1つのポジションのみ開かれます。

追加のインジケーターなしでStockSharpのマーチンゲールマネー管理を実験するためにこの戦略を使用してください。

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>
/// Exponential martingale strategy.
/// </summary>
public class ExpMartinV2Strategy : Strategy
{
	private readonly StrategyParam<decimal> _startVolume;
	private readonly StrategyParam<decimal> _factor;
	private readonly StrategyParam<int> _limit;
	private readonly StrategyParam<int> _stopLoss;
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _startType;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cycleCooldownBars;

	private decimal _currentVolume;
	private decimal _maxVolume;
	private bool _needOpenPosition = true;
	private int _direction;
	private decimal _entryPrice;
	private decimal _longTake;
	private decimal _longStop;
	private decimal _shortTake;
	private decimal _shortStop;
	private int _cooldownRemaining;

	/// <summary>
	/// Initial order volume.
	/// </summary>
	public decimal StartVolume { get => _startVolume.Value; set => _startVolume.Value = value; }

	/// <summary>
	/// Volume multiplier after a loss.
	/// </summary>
	public decimal Factor { get => _factor.Value; set => _factor.Value = value; }

	/// <summary>
	/// Maximum number of volume multiplications.
	/// </summary>
	public int Limit { get => _limit.Value; set => _limit.Value = value; }

	/// <summary>
	/// Stop loss in price steps.
	/// </summary>
	public int StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }

	/// <summary>
	/// Take profit in price steps.
	/// </summary>
	public int TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }

	/// <summary>
	/// Starting order type: 0 - buy, 1 - sell.
	/// </summary>
	public int StartType { get => _startType.Value; set => _startType.Value = value; }

	/// <summary>
	/// Candle type used for processing.
	/// </summary>
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	/// <summary>
	/// Closed candles to wait before starting the next martingale cycle.
	/// </summary>
	public int CycleCooldownBars { get => _cycleCooldownBars.Value; set => _cycleCooldownBars.Value = value; }

	public ExpMartinV2Strategy()
	{
		_startVolume = Param(nameof(StartVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Start Volume", "Initial order volume", "General");

		_factor = Param(nameof(Factor), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Factor", "Volume multiplier", "General");

		_limit = Param(nameof(Limit), 5)
			.SetGreaterThanZero()
			.SetDisplay("Limit", "Max multiplication count", "General");

		_stopLoss = Param(nameof(StopLoss), 100)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Loss limit in points", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 100)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Profit target in points", "Risk");

		_startType = Param(nameof(StartType), 0)
			.SetDisplay("Start Type", "0-Buy, 1-Sell", "General");

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

		_cycleCooldownBars = Param(nameof(CycleCooldownBars), 2)
			.SetNotNegative()
			.SetDisplay("Cycle Cooldown Bars", "Closed candles to wait before the next entry cycle", "General");
	}

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

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

		_currentVolume = 0m;
		_maxVolume = 0m;
		_needOpenPosition = true;
		_direction = 0;
		_entryPrice = 0m;
		_longTake = 0m;
		_longStop = 0m;
		_shortTake = 0m;
		_shortStop = 0m;
		_cooldownRemaining = 0;
	}

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

		StartProtection(null, null);

		_currentVolume = StartVolume;
		_direction = StartType == 0 ? 1 : -1;

		_maxVolume = StartVolume;
		for (var i = 0; i < Limit; i++)
			_maxVolume = RoundVolume(_maxVolume * Factor);

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();
	}

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

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var step = Security.PriceStep ?? 1m;

		if (Position > 0)
		{
			if (candle.HighPrice >= _longTake)
			{
				SellMarket(Position);
				PrepareNext(true);
			}
			else if (candle.LowPrice <= _longStop)
			{
				SellMarket(Position);
				PrepareNext(false);
			}
		}
		else if (Position < 0)
		{
			if (candle.LowPrice <= _shortTake)
			{
				BuyMarket(-Position);
				PrepareNext(true);
			}
			else if (candle.HighPrice >= _shortStop)
			{
				BuyMarket(-Position);
				PrepareNext(false);
			}
		}

		if (_needOpenPosition && Position == 0 && _cooldownRemaining == 0)
		{
			_entryPrice = candle.ClosePrice;
			if (_direction == 1)
			{
				BuyMarket(_currentVolume);
				_longTake = _entryPrice + TakeProfit * step;
				_longStop = _entryPrice - StopLoss * step;
			}
			else
			{
				SellMarket(_currentVolume);
				_shortTake = _entryPrice - TakeProfit * step;
				_shortStop = _entryPrice + StopLoss * step;
			}
			_needOpenPosition = false;
		}
	}

	private void PrepareNext(bool wasProfit)
	{
		if (wasProfit)
		{
			_currentVolume = StartVolume;
		}
		else
		{
			_direction = -_direction;
			_currentVolume = RoundVolume(_currentVolume * Factor);
			if (_currentVolume > _maxVolume)
				_currentVolume = StartVolume;
		}

		_needOpenPosition = true;
		_cooldownRemaining = CycleCooldownBars;
	}

	private decimal RoundVolume(decimal volume)
	{
		var step = Security.VolumeStep ?? 1m;
		return Math.Ceiling(volume / step) * step;
	}
}