GitHub で見る

Ichiオシレーター戦略

概要

  • MetaTrader 5エキスパートExp_ICHI_OSCをStockSharp高レベルAPIに変換。
  • 設定可能なローソク足シリーズで動作し、一目均衡表のラインで構築されたオシレーターからシグナルを導出します。
  • 生のオシレーター値は((Close - SenkouA) - (Tenkan - Kijun)) / Stepで、選択可能な移動平均で平滑化されます。
  • 注文は戦略ボリュームで実行されます。元のコードの複雑な資金管理ブロックはStockSharpのポジション処理に置き換えられました。

パラメーター

名前 説明
CandleType すべてのインジケーター計算に使用されるローソク足時間軸。
IchimokuBase Tenkan(base * 0.5)、Kijun(base * 1.5)、Senkou B(base * 3)の長さを定義する基本期間。
Smoothing Method オシレーターを平滑化するために使用される移動平均。オプション:SimpleExponentialSmoothedWeightedJurikKaufman
Smoothing Length 選択した平滑化方法の期間。
Smoothing Phase 予約済み互換性パラメーター(MQLバージョンから保持、現在組み込み平滑化実装では使用されていない)。
Signal Bar オシレーターの色を読み取るために使用される最新の完成ローソク足からの何バー前か(デフォルト1)。
Enable Buy Entries / Enable Sell Entries それぞれロングまたはショートポジションの開設を許可する。
Enable Buy Exits / Enable Sell Exits 既存のロングまたはショートポジションのクローズを許可する。
Stop Loss (points) 価格ステップで表現された保護ストップ距離。
Take Profit (points) 価格ステップで表現されたテイクプロフィット距離。
Order Volume 成行注文で使用されるベース注文ボリューム。

トレードロジック

  1. 要求されたローソク足シリーズをサブスクライブし、導出された一目均衡表期間を使用してTenkan、Kijun、Senkou Aの値を計算します。
  2. 価格、Senkou A、Tenkan、Kijun間の差からオシレーターを構築し、選択したスムーザーに通します。
  3. 各平滑化された値にカラーコードを割り当てます:
    • 0 — オシレーターがゼロより上で上昇中。
    • 1 — オシレーターがゼロより上で下降中。
    • 2 — ニュートラル(ゼロレベルまたは変化なし)。
    • 3 — オシレーターがゼロより下で減少中。
    • 4 — オシレーターがゼロより下で上昇中。
  4. 2つの色を読みます:SignalBar + 1のバー(前の色)とSignalBarのバー(現在の色)。
    • 前の色が0または3の場合、許可されている場合はショートを閉じて、現在の色が21、または4の場合はロングを開きます。
    • 前の色が4または1の場合、許可されている場合はロングを閉じて、現在の色が01、または3の場合はショートを開きます。
  5. 注文は設定されたボリュームで配置されます。ロングとショートは積み重ねられません:オープンシグナルは同じバーでエグジットロジックが実行された後にのみ評価されます。

リスク管理

  • 保護注文はStartProtectionを通じて管理され、価格ステップでのストップロスとテイクプロフィット距離を使用します。
  • デフォルトではトレーリングや部分的なエグジットは有効になっていません。

注意事項

  • 元の資金管理モジュール(ロット計算、偏差処理、取引タイマー)はStockSharpのポジションおよびボリューム制御に置き換えられます。
  • StockSharpに存在しない平滑化方法(例:JurX、ParMA、VIDYA、T3)は利用できません。提供されたリストから最も近い代替を選択してください。
  • ログのシグナルタイムスタンプには、MQLのTimeShiftSecの使用を反映して、ローソク足クローズ時刻に完全な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>
/// Ichimoku oscillator strategy converted from the MQL Exp_ICHI_OSC expert.
/// Generates entries based on color transitions of the smoothed oscillator derived from Ichimoku lines.
/// </summary>
public class IchiOscillatorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _ichimokuBasePeriod;
	private readonly StrategyParam<SmoothingMethods> _smoothingMethod;
	private readonly StrategyParam<int> _smoothingLength;
	private readonly StrategyParam<int> _smoothingPhase;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<bool> _buyEntriesEnabled;
	private readonly StrategyParam<bool> _sellEntriesEnabled;
	private readonly StrategyParam<bool> _buyExitsEnabled;
	private readonly StrategyParam<bool> _sellExitsEnabled;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<decimal> _orderVolume;

	private Ichimoku _ichimoku = null!;
	private DecimalLengthIndicator _smoother = null!;
	private readonly List<int> _colorHistory = new();
	private decimal? _previousSmoothed;
	private TimeSpan _timeShift;

	/// <summary>
	/// Initializes a new instance of the <see cref="IchiOscillatorStrategy"/> class.
	/// </summary>
	public IchiOscillatorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for Ichimoku calculations", "General");

		_ichimokuBasePeriod = Param(nameof(IchimokuBasePeriod), 22)
			.SetGreaterThanZero()
			.SetDisplay("Ichimoku Base", "Base value to derive Tenkan, Kijun and Senkou spans", "Ichimoku")
			
			.SetOptimize(10, 40, 2);

		_smoothingMethod = Param(nameof(Smoothing), SmoothingMethods.Jurik)
			.SetDisplay("Smoothing Method", "Moving average applied to the oscillator", "Oscillator");

		_smoothingLength = Param(nameof(SmoothingLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Smoothing Length", "Length for oscillator smoothing", "Oscillator")
			
			.SetOptimize(3, 25, 1);

		_smoothingPhase = Param(nameof(SmoothingPhase), 15)
			.SetDisplay("Smoothing Phase", "Additional phase parameter for selected smoothing", "Oscillator");

		_signalBar = Param(nameof(SignalBar), 1)
			.SetNotNegative()
			.SetDisplay("Signal Bar", "Bar shift used for signal confirmation", "Logic");

		_buyEntriesEnabled = Param(nameof(BuyEntriesEnabled), true)
			.SetDisplay("Enable Buy Entries", "Allow opening long positions", "Logic");

		_sellEntriesEnabled = Param(nameof(SellEntriesEnabled), true)
			.SetDisplay("Enable Sell Entries", "Allow opening short positions", "Logic");

		_buyExitsEnabled = Param(nameof(BuyExitsEnabled), true)
			.SetDisplay("Enable Buy Exits", "Allow closing long positions", "Logic");

		_sellExitsEnabled = Param(nameof(SellExitsEnabled), true)
			.SetDisplay("Enable Sell Exits", "Allow closing short positions", "Logic");

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
			.SetNotNegative()
			.SetDisplay("Stop Loss (points)", "Protective stop distance in price steps", "Risk Management")
			
			.SetOptimize(200, 2000, 200);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
			.SetNotNegative()
			.SetDisplay("Take Profit (points)", "Protective take-profit distance in price steps", "Risk Management")
			
			.SetOptimize(200, 4000, 200);

		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Base order volume for market orders", "General");
	}

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

	/// <summary>
	/// Base Ichimoku period that controls Tenkan, Kijun and Senkou lengths.
	/// </summary>
	public int IchimokuBasePeriod
	{
		get => _ichimokuBasePeriod.Value;
		set => _ichimokuBasePeriod.Value = value;
	}

	/// <summary>
	/// Smoothing method applied to the oscillator.
	/// </summary>
	public SmoothingMethods Smoothing
	{
		get => _smoothingMethod.Value;
		set => _smoothingMethod.Value = value;
	}

	/// <summary>
	/// Oscillator smoothing length.
	/// </summary>
	public int SmoothingLength
	{
		get => _smoothingLength.Value;
		set => _smoothingLength.Value = value;
	}

	/// <summary>
	/// Phase parameter for smoothing algorithms that support it.
	/// </summary>
	public int SmoothingPhase
	{
		get => _smoothingPhase.Value;
		set => _smoothingPhase.Value = value;
	}

	/// <summary>
	/// Bar offset used to confirm oscillator color transitions.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <summary>
	/// Enable opening of long positions.
	/// </summary>
	public bool BuyEntriesEnabled
	{
		get => _buyEntriesEnabled.Value;
		set => _buyEntriesEnabled.Value = value;
	}

	/// <summary>
	/// Enable opening of short positions.
	/// </summary>
	public bool SellEntriesEnabled
	{
		get => _sellEntriesEnabled.Value;
		set => _sellEntriesEnabled.Value = value;
	}

	/// <summary>
	/// Enable closing of existing long positions.
	/// </summary>
	public bool BuyExitsEnabled
	{
		get => _buyExitsEnabled.Value;
		set => _buyExitsEnabled.Value = value;
	}

	/// <summary>
	/// Enable closing of existing short positions.
	/// </summary>
	public bool SellExitsEnabled
	{
		get => _sellExitsEnabled.Value;
		set => _sellExitsEnabled.Value = value;
	}

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

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

	/// <summary>
	/// Base volume used for market orders.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set
		{
			_orderVolume.Value = value;
			Volume = value;
		}
	}

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

		_colorHistory.Clear();
		_previousSmoothed = null;
		_ichimoku?.Reset();
		_smoother?.Reset();
		_timeShift = TimeSpan.Zero;
	}

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

		Volume = OrderVolume;

		var tenkanLength = Math.Max(1, (int)(IchimokuBasePeriod * 0.5m));
		var kijunLength = Math.Max(1, (int)(IchimokuBasePeriod * 1.5m));
		var senkouBLength = Math.Max(1, (int)(IchimokuBasePeriod * 3m));

		_ichimoku = new Ichimoku
		{
			Tenkan = { Length = tenkanLength },
			Kijun = { Length = kijunLength },
			SenkouB = { Length = senkouBLength }
		};

		_smoother = CreateSmoother(Smoothing, SmoothingLength, SmoothingPhase);

		_timeShift = CandleType.Arg is TimeSpan span && span > TimeSpan.Zero ? span : TimeSpan.Zero;

		_colorHistory.Clear();
		_previousSmoothed = null;

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _smoother);
			DrawOwnTrades(area);
		}

		StartProtection(
			StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null,
			TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null);
	}

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

		var ichimokuTyped = (IchimokuValue)ichimokuValue;

		if (ichimokuTyped.Tenkan is not decimal tenkan ||
			ichimokuTyped.Kijun is not decimal kijun ||
			ichimokuTyped.SenkouA is not decimal senkouA)
		{
			return;
		}

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

		var markt = candle.ClosePrice - senkouA;
		var trend = tenkan - kijun;
		var rawOscillator = (markt - trend) / step;

		var smoothValue = _smoother.Process(new DecimalIndicatorValue(_smoother, rawOscillator, candle.OpenTime) { IsFinal = true });
		if (!smoothValue.IsFinal || smoothValue is not DecimalIndicatorValue smoothResult)
			return;

		var smoothed = smoothResult.Value;
		UpdateColorHistory(smoothed);

		if (_colorHistory.Count <= SignalBar + 1)
			return;

		var currentIndex = _colorHistory.Count - 1 - SignalBar;
		var previousIndex = currentIndex - 1;
		if (previousIndex < 0)
			return;

		var currentColor = _colorHistory[currentIndex];
		var previousColor = _colorHistory[previousIndex];

		var buyOpen = false;
		var sellOpen = false;
		var buyClose = false;
		var sellClose = false;

		if (previousColor == 0 || previousColor == 3)
		{
			sellClose = SellExitsEnabled;

			if (BuyEntriesEnabled && (currentColor == 2 || currentColor == 1 || currentColor == 4))
				buyOpen = true;
		}

		if (previousColor == 4 || previousColor == 1)
		{
			buyClose = BuyExitsEnabled;

			if (SellEntriesEnabled && (currentColor == 0 || currentColor == 1 || currentColor == 3))
				sellOpen = true;
		}

		var signalTime = candle.CloseTime + _timeShift;

		if (buyClose && Position > 0)
		{
			SellMarket();
			this.LogInfo($"[{signalTime}] Closing long at {candle.ClosePrice} due to oscillator color change {previousColor}->{currentColor}.");
		}

		if (sellClose && Position < 0)
		{
			BuyMarket();
			this.LogInfo($"[{signalTime}] Closing short at {candle.ClosePrice} due to oscillator color change {previousColor}->{currentColor}.");
		}

		if (buyOpen && Position <= 0)
		{
			var volume = Volume + Math.Max(0m, -Position);
			BuyMarket();
			this.LogInfo($"[{signalTime}] Opening long at {candle.ClosePrice} with oscillator {smoothed:F5}.");
		}

		if (sellOpen && Position >= 0)
		{
			var volume = Volume + Math.Max(0m, Position);
			SellMarket();
			this.LogInfo($"[{signalTime}] Opening short at {candle.ClosePrice} with oscillator {smoothed:F5}.");
		}
	}

	private void UpdateColorHistory(decimal smoothed)
	{
		var color = 2;

		if (_previousSmoothed.HasValue)
		{
			var prev = _previousSmoothed.Value;

			if (smoothed > 0m)
			{
				if (prev < smoothed)
					color = 0;
				else if (prev > smoothed)
					color = 1;
			}
			else if (smoothed < 0m)
			{
				if (prev < smoothed)
					color = 4;
				else if (prev > smoothed)
					color = 3;
			}
		}
		else
		{
			if (smoothed > 0m)
				color = 0;
			else if (smoothed < 0m)
				color = 3;
		}

		_colorHistory.Add(color);
		_previousSmoothed = smoothed;
	}

	private DecimalLengthIndicator CreateSmoother(SmoothingMethods method, int length, int phase)
	{
		return method switch
		{
			SmoothingMethods.Simple => new SMA { Length = length },
			SmoothingMethods.Exponential => new EMA { Length = length },
			SmoothingMethods.Smoothed => new SmoothedMovingAverage { Length = length },
			SmoothingMethods.Weighted => new WeightedMovingAverage { Length = length },
			SmoothingMethods.Jurik => new JurikMovingAverage { Length = length },
			SmoothingMethods.Kaufman => new KaufmanAdaptiveMovingAverage { Length = length },
			_ => new JurikMovingAverage { Length = length }
		};
	}

	/// <summary>
	/// Supported smoothing algorithms for the oscillator.
	/// </summary>
	public enum SmoothingMethods
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Simple,

		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Exponential,

		/// <summary>
		/// Smoothed moving average.
		/// </summary>
		Smoothed,

		/// <summary>
		/// Weighted moving average.
		/// </summary>
		Weighted,

		/// <summary>
		/// Jurik moving average.
		/// </summary>
		Jurik,

		/// <summary>
		/// Kaufman adaptive moving average.
		/// </summary>
		Kaufman
	}
}