Auf GitHub ansehen

Genie Pivot-Strategie

Diese Strategie setzt die Genie Pivot Umkehr-Scalping-Idee um, die ursprünglich in MQL4 geschrieben wurde. Sie wartet auf ein Pivot-Muster, das aus sieben aufeinanderfolgenden Kerzen besteht, und verwaltet die offene Position mit einem festen Take-Profit und einem Trailing-Stop.

Strategielogik

  1. Mustererkennung – ein Long-Signal erscheint, wenn die sieben vorherigen Tiefs streng fallend sind und die letzte abgeschlossene Kerze ein höheres Tief mit einem Schluss oberhalb des vorherigen Hochs bildet. Ein Short-Signal wird durch die gespiegelte Bedingung bei den Hochs generiert.
  2. Orderausführung – sobald ein Signal bestätigt ist, öffnet die Strategie eine Marktorder mit dem Volumen, das aus dem Kontokapital und den konfigurierten Risikoparametern berechnet wird.
  3. Trade-Management – nach dem Einstieg werden ein Take-Profit und ein Trailing-Stop gesetzt. Der Trailing-Stop wird nur aktiviert, wenn der Gewinn die Trailing-Distanz überschreitet. Kehrt der Preis bei der folgenden Kerze um (Bärenkerze für Long, Bullenkerze für Short), wird die Position sofort geschlossen.
  4. Volumenreduktion – aufeinanderfolgende Verlustgeschäfte reduzieren das gehandelte Volumen gemäß dem Parameter Decrease Factor.

Parameter

Name Beschreibung
TakeProfit Gewinnziel in Preisschritten vom Einstiegspreis.
TrailingStop Trailing-Stop-Abstand in Preisschritten.
MaximumRisk Anteil des Kontowerts für die Positionsgrößenbestimmung.
DecreaseFactor Reduziert das Volumen nach aufeinanderfolgenden Verlusten.
BaseVolume Ausweichvolumen, wenn der Portfoliowert unbekannt ist.
CandleType Zeitrahmen der zu analysierenden Kerzen.

Hinweise

Die Strategie verarbeitet nur abgeschlossene Kerzen. Eine Python-Version ist noch nicht vorhanden.

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>
/// Pivot point reversal scalping strategy.
/// </summary>
public class GeniePivotStrategy : Strategy
{
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<decimal> _maximumRisk;
	private readonly StrategyParam<decimal> _decreaseFactor;
	private readonly StrategyParam<decimal> _baseVolume;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;

	private readonly decimal[] _lows = new decimal[8];
	private readonly decimal[] _highs = new decimal[8];
	private int _filled;

	private decimal _entryPrice;
	private decimal _stopPrice;
	private decimal _targetPrice;
	private int _lossCount;
	private int _cooldownRemaining;

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

	/// <summary>
	/// Trailing stop distance in price steps.
	/// </summary>
	public decimal TrailingStop
	{
		get => _trailingStop.Value;
		set => _trailingStop.Value = value;
	}

	/// <summary>
	/// Maximum risk used to calculate volume.
	/// </summary>
	public decimal MaximumRisk
	{
		get => _maximumRisk.Value;
		set => _maximumRisk.Value = value;
	}

	/// <summary>
	/// Factor to decrease volume after consecutive losses.
	/// </summary>
	public decimal DecreaseFactor
	{
		get => _decreaseFactor.Value;
		set => _decreaseFactor.Value = value;
	}

	/// <summary>
	/// Base volume used when account value is unknown.
	/// </summary>
	public decimal BaseVolume
	{
		get => _baseVolume.Value;
		set => _baseVolume.Value = value;
	}

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

	/// <summary>
	/// Number of completed candles to wait after closing a position.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="GeniePivotStrategy"/>.
	/// </summary>
	public GeniePivotStrategy()
	{
		_takeProfit = Param(nameof(TakeProfit), 500m).SetDisplay("Take Profit", "Profit target in points", "Risk");

		_trailingStop =
			Param(nameof(TrailingStop), 200m).SetDisplay("Trailing Stop", "Trailing distance in points", "Risk");

		_maximumRisk = Param(nameof(MaximumRisk), 0.02m).SetDisplay("Maximum Risk", "Risk per trade", "Risk");

		_decreaseFactor =
			Param(nameof(DecreaseFactor), 3m).SetDisplay("Decrease Factor", "Volume reduction after losses", "Risk");

		_baseVolume = Param(nameof(BaseVolume), 0.1m).SetDisplay("Base Volume", "Fallback volume", "General");

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

		_cooldownBars = Param(nameof(CooldownBars), 4)
			.SetDisplay("Cooldown Bars", "Completed candles to wait after closing a position", "Risk");
	}

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

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

		_filled = 0;
		_entryPrice = default;
		_stopPrice = default;
		_targetPrice = default;
		_lossCount = 0;
		_cooldownRemaining = 0;

		Array.Clear(_lows);
		Array.Clear(_highs);
	}

	/// <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 decimal GetVolume()
	{
		var lot = BaseVolume;

		if (MaximumRisk > 0m && Portfolio.CurrentValue is decimal value)
			lot = Math.Round(value * MaximumRisk / 1000m, 1);

		if (DecreaseFactor > 0m && _lossCount > 1)
			lot -= lot * _lossCount / DecreaseFactor;

		return lot < 0.1m ? 0.1m : lot;
	}

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

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var step = Security.PriceStep ?? 1m;
		var close = candle.ClosePrice;
		var open = candle.OpenPrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		// shift history
		for (var i = _lows.Length - 1; i > 0; i--)
		{
			_lows[i] = _lows[i - 1];
			_highs[i] = _highs[i - 1];
		}
		_lows[0] = low;
		_highs[0] = high;

		if (_filled < 4)
		{
			_filled++;
			return;
		}

		if (Position == 0)
		{
			if (_cooldownRemaining > 0)
				return;

			// Buy when 3 consecutive declining lows followed by a higher low (reversal)
			var buyCond = _lows[4] > _lows[3] && _lows[3] > _lows[2] && _lows[2] > _lows[1] &&
						  _lows[1] < _lows[0] && close > _highs[1] && close > open;

			// Sell when 3 consecutive rising highs followed by a lower high (reversal)
			var sellCond = _highs[4] < _highs[3] && _highs[3] < _highs[2] && _highs[2] < _highs[1] &&
						   _highs[1] > _highs[0] && close < _lows[1] && close < open;

			if (buyCond)
			{
				BuyMarket();
				_entryPrice = close;
				_stopPrice = _entryPrice - TrailingStop * step;
				_targetPrice = _entryPrice + TakeProfit * step;
			}
			else if (sellCond)
			{
				SellMarket();
				_entryPrice = close;
				_stopPrice = _entryPrice + TrailingStop * step;
				_targetPrice = _entryPrice - TakeProfit * step;
			}
		}
		else if (Position > 0)
		{
			if (close >= _targetPrice)
			{
				SellMarket();
				_lossCount = 0;
				_cooldownRemaining = CooldownBars;
			}
			else
			{
				if (close - _entryPrice > TrailingStop * step)
				{
					var newStop = close - TrailingStop * step;
					if (newStop > _stopPrice)
						_stopPrice = newStop;
				}

				if (low <= _stopPrice)
				{
					SellMarket();
					_lossCount++;
					_cooldownRemaining = CooldownBars;
				}
			}
		}
		else if (Position < 0)
		{
			if (close <= _targetPrice)
			{
				BuyMarket();
				_lossCount = 0;
				_cooldownRemaining = CooldownBars;
			}
			else
			{
				if (_entryPrice - close > TrailingStop * step)
				{
					var newStop = close + TrailingStop * step;
					if (newStop < _stopPrice)
						_stopPrice = newStop;
				}

				if (high >= _stopPrice)
				{
					BuyMarket();
					_lossCount++;
					_cooldownRemaining = CooldownBars;
				}
			}
		}
	}
}