GitHub で見る

Pipsover 戦略

概要

Pipsover は Chaikin オシレーターの強い極値に反応するモメンタムリバーサル戦略です。オリジナルの MetaTrader 5 Expert Advisor は、オシレーターが著しいスパイクを印刷し、前のローソク足が 20 期間単純移動平均に引き戻るときに新しいトレードを開きます。C# ポートは、累積/分配ライン(ADL)と 2 つの指数移動平均で Chaikin オシレーターを再構築して同じアイデアを維持します。各トレードはリスク管理が参照実装と一致するよう、スクリプトで定義された同じストップロスとテイクプロフィットの距離で保護されます。

インジケーターとツール

  • 単純移動平均(SMA 20) – 平均回帰アンカーを提供します。戦略は前のローソク足がトレードの対象となる前に平均に触れるかクロスすることを必要とします。
  • Chaikin オシレーター(ADL の EMA 3 – EMA 10) – 価格とボリュームの間の圧力を測定します。極端な負の読みはロングの機会を引き起こし、極端な正の値はショートの機会を引き起こします。
  • 累積/分配ライン(ADL) – Chaikin オシレーターを供給します。高速と低速の EMA がこの値ストリームで実行され、MQL5 の iChaikin インジケーターを模倣します。

トレードロジック

ロングエントリー

  1. すべてのインジケーター値が確定するよう、完了したローソク足を待ちます。
  2. 前のローソク足が強気で終了した(Close > Open)ことを確認します。
  3. 前の安値が SMA20 を下回り、プルバックを示すことを確認します。
  4. 前のバーの Chaikin オシレーター値を読み取ります。売られ過ぎのスパイクを反映するために -OpenLevel より低い必要があります。
  5. すべての条件が満たされ、現在ポジションが開いていない場合、成行買い注文を送信します。

ショートエントリー

  1. 完了したローソク足を待ちます。
  2. 前のローソク足が弱気で終了した(Close < Open)ことを確認します。
  3. 前の高値が SMA20 を超えたことを確認します。
  4. 前のバーの Chaikin オシレーターが OpenLevel より大きいことを確認します。
  5. アクティブなポジションがない場合、成行売り注文を出します。

決済ロジック

  • ロングポジションはエントリー後の次のローソク足が弱気構造(終値が始値を下回る)を示し、その高値が SMA20 を上回り、Chaikin オシレーターが CloseLevel を上回ると決済されます。
  • ショートポジションは次のローソク足が強気構造を示し、その安値が SMA20 を下回り、Chaikin オシレーターが -CloseLevel を下回ると決済されます。
  • 保護決済は完了した各ローソク足を監視します。ロングは価格が計算されたストップロス以下またはテイクプロフィット以上でトレードすると決済されます。ショートでは比較が反転されます。

ポジション管理

  • 一度に 1 つのネットポジションのみ許可されます。MQL5 の単一ポジション動作を再現するため、新しいトレードを開く前に保留注文がキャンセルされます。
  • ストップロスとテイクプロフィットの値はセキュリティの価格ステップから計算されます。ロングの場合、ストップは実行価格の StopLossPoints * PriceStep 下に設定され、テイクプロフィットは TakeProfitPoints * PriceStep 上に設定されます。ショートは対称的だが反転した距離を使用します。

パラメーター

名前 デフォルト 説明
TradeVolume 0.1 すべての成行注文に使用される注文サイズ。
MaLength 20 プルバック SMA の期間。
StopLossPoints 65 エントリーからの価格ステップ単位のストップロスオフセット。
TakeProfitPoints 100 エントリーからの価格ステップ単位のテイクプロフィットオフセット。
OpenLevel 100 新しいエントリーを有効にする絶対 Chaikin 閾値。
CloseLevel 125 ポジション決済を強制する絶対 Chaikin 閾値。
ChaikinFastLength 3 Chaikin オシレーターの高速 EMA の長さ。
ChaikinSlowLength 10 Chaikin オシレーターの低速 EMA の長さ。
CandleType 1 時間 ローソク足サブスクリプションに使用される時間軸。関心のあるトレーディングセッションに合わせて調整します。

実装メモ

  • 戦略は SubscribeCandles().Bind(...) を通じて累積/分配ラインと SMA をローソク足フィードにバインドし、インジケーター値が完了した各ローソク足と同期して届くことを保証します。
  • Chaikin 値は変換ガイドラインで禁じられた低レベルバッファアクセスを避けるため、ProcessCandle 内で手動で再構築されます。
  • アルゴリズムは最後の完了ローソク足、SMA 値、Chaikin 読み取りを保存して、MQL5 スクリプトで使用された shift=1 ロジック(iClose(...,1), iLow(...,1), iChaikin(...,1))を再現します。
  • 保護ターゲットレベルはブローカー管理のストップに依存するのではなく、戦略クラス内で追跡されるため、シミュレーションとライブトレードで動作が一貫しています。
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>
/// Pipsover strategy rebuilt on the high level StockSharp API.
/// The strategy opens positions when a strong Chaikin oscillator spike aligns with a pullback to the 20-period SMA on the previous candle.
/// Protective stop-loss and take-profit levels are recreated using price step distances defined in the original Expert Advisor.
/// </summary>
public class PipsoverStrategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<int> _maLength;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _openLevel;
	private readonly StrategyParam<decimal> _closeLevel;
	private readonly StrategyParam<int> _chaikinFastLength;
	private readonly StrategyParam<int> _chaikinSlowLength;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _sma;
	private AccumulationDistributionLine _accumulationDistribution;
	private ExponentialMovingAverage _chaikinFast;
	private ExponentialMovingAverage _chaikinSlow;

	private decimal _prevOpen;
	private decimal _prevHigh;
	private decimal _prevLow;
	private decimal _prevClose;
	private decimal _prevSma;
	private decimal _prevChaikin;
	private bool _hasPrevCandle;

	private decimal _stopPrice;
	private decimal _takeProfitPrice;
	private bool _hasTargets;

	/// <summary>
	/// Trading volume used for market orders.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Period of the simple moving average that acts as a pullback filter.
	/// </summary>
	public int MaLength
	{
		get => _maLength.Value;
		set => _maLength.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in price steps.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in price steps.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Chaikin oscillator level required to allow new entries.
	/// </summary>
	public decimal OpenLevel
	{
		get => _openLevel.Value;
		set => _openLevel.Value = value;
	}

	/// <summary>
	/// Chaikin oscillator level that closes existing positions.
	/// </summary>
	public decimal CloseLevel
	{
		get => _closeLevel.Value;
		set => _closeLevel.Value = value;
	}

	/// <summary>
	/// Fast EMA period for Chaikin oscillator reconstruction.
	/// </summary>
	public int ChaikinFastLength
	{
		get => _chaikinFastLength.Value;
		set => _chaikinFastLength.Value = value;
	}

	/// <summary>
	/// Slow EMA period for Chaikin oscillator reconstruction.
	/// </summary>
	public int ChaikinSlowLength
	{
		get => _chaikinSlowLength.Value;
		set => _chaikinSlowLength.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="PipsoverStrategy"/> class.
	/// </summary>
	public PipsoverStrategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order size used for market entries", "Trading");

		_maLength = Param(nameof(MaLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("SMA Length", "Simple moving average length", "Indicators");

		_stopLossPoints = Param(nameof(StopLossPoints), 65m)
			.SetGreaterThanZero()
			.SetDisplay("Stop-Loss Points", "Stop-loss distance expressed in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 100m)
			.SetGreaterThanZero()
			.SetDisplay("Take-Profit Points", "Take-profit distance expressed in price steps", "Risk");

		_openLevel = Param(nameof(OpenLevel), 20m)
			.SetGreaterThanZero()
			.SetDisplay("Open Level", "Chaikin oscillator threshold for entries", "Chaikin");

		_closeLevel = Param(nameof(CloseLevel), 30m)
			.SetGreaterThanZero()
			.SetDisplay("Close Level", "Chaikin oscillator threshold for exits", "Chaikin");

		_chaikinFastLength = Param(nameof(ChaikinFastLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Chaikin Fast Length", "Fast EMA length for Chaikin oscillator", "Chaikin");

		_chaikinSlowLength = Param(nameof(ChaikinSlowLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("Chaikin Slow Length", "Slow EMA length for Chaikin oscillator", "Chaikin");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for calculations", "Data");
	}

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

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

		// Reset cached candle and indicator state.
		_hasPrevCandle = false;
		_prevOpen = 0m;
		_prevHigh = 0m;
		_prevLow = 0m;
		_prevClose = 0m;
		_prevSma = 0m;
		_prevChaikin = 0m;

		// Reset protective price levels.
		_stopPrice = 0m;
		_takeProfitPrice = 0m;
		_hasTargets = false;
	}

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

		// Apply configured trading volume to base strategy.
		Volume = TradeVolume;

		// Prepare indicators that replicate the MQL Expert Advisor logic.
		_sma = new SMA { Length = MaLength };
		_accumulationDistribution = new AccumulationDistributionLine();
		_chaikinFast = new EMA { Length = ChaikinFastLength };
		_chaikinSlow = new EMA { Length = ChaikinSlowLength };

		// Subscribe to candle data and bind indicators.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_accumulationDistribution, _sma, ProcessCandle)
			.Start();

		// Optional charting for visual validation.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _sma);
			DrawIndicator(area, _accumulationDistribution);
			DrawOwnTrades(area);
		}
	}

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

		// Rebuild Chaikin oscillator values via EMA of the ADL indicator.
		var fastResult = _chaikinFast.Process(new DecimalIndicatorValue(_chaikinFast, adlValue, candle.ServerTime) { IsFinal = true });
		var slowResult = _chaikinSlow.Process(new DecimalIndicatorValue(_chaikinSlow, adlValue, candle.ServerTime) { IsFinal = true });
		var chaikinValue = fastResult.ToDecimal() - slowResult.ToDecimal();

		// Wait for all indicators to be fully formed before trading.
		if (!_chaikinFast.IsFormed || !_chaikinSlow.IsFormed || !_sma.IsFormed)
		{
			UpdateState(candle, chaikinValue, smaValue);
			return;
		}

		if (!_hasPrevCandle)
		{
			UpdateState(candle, chaikinValue, smaValue);
			return;
		}

		var step = Security?.PriceStep ?? 1m;
		var stopLossDistance = StopLossPoints * step;
		var takeProfitDistance = TakeProfitPoints * step;

		// Check protective stop-loss and take-profit targets before new decisions.
		if (_hasTargets)
		{
			if (Position > 0)
			{
				if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takeProfitPrice)
				{
					SellMarket(Position);
					ResetTargets();
				}
			}
			else if (Position < 0)
			{
				if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takeProfitPrice)
				{
					BuyMarket(Math.Abs(Position));
					ResetTargets();
				}
			}
			else
			{
				ResetTargets();
			}
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			UpdateState(candle, chaikinValue, smaValue);
			return;
		}

		var prevBullish = _prevClose > _prevOpen;
		var prevBearish = _prevClose < _prevOpen;

		if (Position > 0)
		{
			var shouldExitLong = prevBearish && _prevHigh > _prevSma && _prevChaikin > CloseLevel;
			if (shouldExitLong)
			{
				SellMarket(Position);
				ResetTargets();
			}
		}
		else if (Position < 0)
		{
			var shouldExitShort = prevBullish && _prevLow < _prevSma && _prevChaikin < -CloseLevel;
			if (shouldExitShort)
			{
				BuyMarket(Math.Abs(Position));
				ResetTargets();
			}
		}
		else
		{
			// No position is open, evaluate entry signals.

			var allowLong = prevBullish && _prevLow < _prevSma && _prevChaikin < -OpenLevel;
			var allowShort = prevBearish && _prevHigh > _prevSma && _prevChaikin > OpenLevel;

			if (allowLong)
			{
				BuyMarket();

				var entryPrice = candle.ClosePrice;
				_stopPrice = entryPrice - stopLossDistance;
				_takeProfitPrice = entryPrice + takeProfitDistance;
				_hasTargets = true;
			}
			else if (allowShort)
			{
				SellMarket();

				var entryPrice = candle.ClosePrice;
				_stopPrice = entryPrice + stopLossDistance;
				_takeProfitPrice = entryPrice - takeProfitDistance;
				_hasTargets = true;
			}
		}

		UpdateState(candle, chaikinValue, smaValue);
	}

	private void UpdateState(ICandleMessage candle, decimal chaikinValue, decimal smaValue)
	{
		// Store previous candle data for next iteration checks.
		_prevOpen = candle.OpenPrice;
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_prevClose = candle.ClosePrice;
		_prevSma = smaValue;
		_prevChaikin = chaikinValue;
		_hasPrevCandle = true;
	}

	private void ResetTargets()
	{
		// Clear stop-loss and take-profit levels once position is closed.
		_stopPrice = 0m;
		_takeProfitPrice = 0m;
		_hasTargets = false;
	}
}