GitHub で見る

OSFのカウンタートレンド戦略

この戦略は、オープンソース外国為替の「買われすぎ/売られすぎ」逆トレンドの専門家を再現しています。 いくつかの RSI の読み取り値を平均して元の発振器を近似し、解釈します 平衡レベル (50) からの距離を方向と位置の両方のサイズ信号として表示します。 取引は完成したローソク足で実行され、固定のテイクプロフィットで終了します。 楽器のポイント。

取引ルール

  • データ: 構成された CandleType の完成したキャンドル。
  • インジケーター: RSI、期間は RsiPeriod で定義されます。元の MQL 専門家は平均 5 RSI 値は同一であるため、ここでは単一の RSI で十分です。
  • 信号ロジック:
    • RSI > 50 の場合、市場は買われすぎとみなされ、ショート ポジションがオープンされます。
    • RSI < 50 の場合、市場は売られ過ぎとみなされ、ロングポジションがオープンされます。
    • 絶対距離 |RSI − 50| VolumePerPoint を通じて取引量が決まります。
  • クールダウン: 各取引後、戦略は CooldownBars 個のローソク足が終了するまで待機します。 新しいエントリを評価しています。これは、ソース コードのバー スムージング動作を模倣します。
  • エグジット: 各エントリーは、手動テイクプロフィットを TakeProfitPoints * PriceStep 離れた場所に配置します。 約定価格。オリジナルのエキスパートとまったく同様に、ストップロスは使用されません。
  • 反転: 反対方向に取引を開始すると、既存のポジションがあれば最初に決済されます。 成行注文量を調整することによって。

パラメーター

パラメータ 説明
RsiPeriod OSF オシレーターを近似するために使用される RSI の長さ (デフォルトは 14)。
VolumePerPoint 50 レベルから離れた RSI ポイントごとに取引されるボリューム (デフォルトは 0.01)。
TakeProfitPoints 商品ポイントで表されるテイクプロフィットターゲットまでの距離 (デフォルトは 150)。
CooldownBars 各取引後にスキップする完了したローソク足の数 (デフォルトは 5)。
CandleType インジケーター計算のローソク足タイプ (デフォルトの 1 分の時間枠)。

注意事項

  • この戦略では、選択した金融商品に対して PriceStep が定義されていることを前提としています。それ以外の場合はユニット 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>
/// Countertrend strategy based on the Open Source Forex oscillator.
/// </summary>
public class OsfCountertrendStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _volumePerPoint;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi;
	private int _cooldown;
	private decimal _longTarget;
	private decimal _shortTarget;

	/// <summary>
	/// RSI period used to approximate the original OSF oscillator.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// Volume traded per RSI point away from equilibrium (50 level).
	/// </summary>
	public decimal VolumePerPoint
	{
		get => _volumePerPoint.Value;
		set => _volumePerPoint.Value = value;
	}

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

	/// <summary>
	/// Number of finished candles to wait before a new signal can trigger.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of <see cref="OsfCountertrendStrategy"/>.
	/// </summary>
	public OsfCountertrendStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetRange(2, 200)
		
		.SetDisplay("RSI Period", "RSI length used in oscillator", "General");

		_volumePerPoint = Param(nameof(VolumePerPoint), 0.01m)
		.SetRange(0.001m, 1m)
		
		.SetDisplay("Volume per Point", "Order volume per RSI point from 50", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 150m)
		.SetRange(0m, 1000m)
		
		.SetDisplay("Take Profit", "Distance to take profit in points", "Risk");

		_cooldownBars = Param(nameof(CooldownBars), 5)
		.SetRange(0, 50)
		
		.SetDisplay("Cooldown Bars", "Finished candles to wait after trading", "General");

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

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

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

		_rsi = null;
		_cooldown = 0;
		_longTarget = 0m;
		_shortTarget = 0m;
	}

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

		_rsi = new RelativeStrengthIndex
		{
			Length = RsiPeriod
		};

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

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

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

		if (!_rsi.IsFormed)
		return;

		// indicators already checked above

		// Track active positions for manual take-profit handling.
		if (Position > 0 && _longTarget > 0m && TakeProfitPoints > 0m)
		{
			if (candle.LowPrice <= _longTarget)
			{
				SellMarket();
				_longTarget = 0m;
			}
		}
		else if (Position < 0 && _shortTarget > 0m && TakeProfitPoints > 0m)
		{
			if (candle.HighPrice >= _shortTarget)
			{
				BuyMarket();
				_shortTarget = 0m;
			}
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		var diff = rsiValue - 50m;
		if (diff == 0m)
		return;

		var absDiff = Math.Abs(diff);
		var volume = absDiff * VolumePerPoint;
		if (volume <= 0m)
		return;

		var step = Security?.PriceStep ?? 1m;

		if (diff > 0m && Position <= 0m)
		{
			// RSI above 50: countertrend short trade sized by oscillator distance.
			var volumeToSell = volume + Math.Max(0m, Position);
			if (volumeToSell <= 0m)
			return;

			SellMarket();

			_shortTarget = TakeProfitPoints > 0m
			? candle.ClosePrice - step * TakeProfitPoints
			: 0m;
			_longTarget = 0m;
			_cooldown = CooldownBars;
		}
		else if (diff < 0m && Position >= 0m)
		{
			// RSI below 50: countertrend long trade sized by oscillator distance.
			var volumeToBuy = volume + Math.Max(0m, -Position);
			if (volumeToBuy <= 0m)
			return;

			BuyMarket();

			_longTarget = TakeProfitPoints > 0m
			? candle.ClosePrice + step * TakeProfitPoints
			: 0m;
			_shortTarget = 0m;
			_cooldown = CooldownBars;
		}
	}
}