Ver no GitHub

Estratégia Contratendência da OSF

A estratégia reproduz o especialista em contratendência Open Source Forex "Overbought/Oversold". Ele se aproxima do oscilador original calculando a média de várias RSI leituras e interpretações a distância do nível de equilíbrio (50) como sinal de direção e tamanho de posição. As negociações são executadas em velas finalizadas e fechadas por um take-profit fixo medido em pontos do instrumento.

Regras de negociação

  • Dados: Candles finalizadas do CandleType configurado.
  • Indicador: RSI com período definido por RsiPeriod. O especialista MQL original teve média de cinco valores RSI idênticos, portanto, um único RSI é suficiente aqui.
  • Lógica de sinal:
    • Quando RSI > 50, o mercado é considerado sobrecomprado e uma posição curta é aberta.
    • Quando RSI < 50, o mercado é considerado sobrevendido e uma posição longa é aberta.
    • A distância absoluta |RSI − 50| determina o volume negociado por meio de VolumePerPoint.
  • Cooldown: Após cada negociação, a estratégia espera por CooldownBars velas concluídas antes avaliando uma nova entrada. Isso imita o comportamento de suavização de barra do código-fonte.
  • Saídas: cada entrada coloca um take-profit manual a TakeProfitPoints * PriceStep de distância de o preço de preenchimento. Nenhum stop loss é usado, exatamente como no especialista original.
  • Reversões: Abrir uma negociação na direção oposta fecha primeiro qualquer posição existente ajustando o volume da ordem de mercado.

Parâmetros

Parâmetro Descrição
RsiPeriod Comprimento RSI usado para aproximar o oscilador OSF (padrão 14).
VolumePerPoint Volume negociado para cada RSI ponto fora do nível 50 (padrão 0,01).
TakeProfitPoints Distância até a meta de lucro expressa em pontos de instrumento (padrão 150).
CooldownBars Número de velas concluídas a serem ignoradas após cada negociação (padrão 5).
CandleType Tipo de vela para cálculos de indicadores (período padrão de 1 minuto).

Notas

  • A estratégia assume que PriceStep está definido para o instrumento selecionado; caso contrário, uma unidade a etapa 1 é usada para calcular o nível de lucro.
  • Como o especialista original não tinha stop-loss de proteção, o gerenciamento de risco deveria ser adicionado manualmente ao implantar a estratégia ao vivo.
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;
		}
	}
}