Esta estratégia é uma versão StockSharp do consultor especialista MetaTrader "DeMarker ganhando volume de posição". Ele usa o oscilador DeMarker para detectar extremos de sobrevenda e sobrecompra, acumulando gradualmente exposição quando o mercado permanece em uma condição esticada. A implementação opera em velas completadas e garante que apenas um sinal por barra seja processado.
A versão C# concentra-se na lógica discricionária central do script original enquanto adota o StockSharp API de alto nível. O gerenciamento de pedidos, o crescimento do volume e o comportamento de reversão opcional estão disponíveis por meio de parâmetros estratégicos, permitindo que o algoritmo seja adaptado a diferentes mercados e prazos.
Parâmetros
Período DeMarker – número de velas usadas pelo indicador DeMarker.
Nível superior – limite do oscilador que prepara exposição curta (padrão 0.7).
Nível inferior – limite do oscilador que prepara exposição longa (padrão 0.3).
Volume de negociação – volume de ordens de mercado enviadas em cada sinal.
Apenas uma posição – quando habilitada, a estratégia se estabiliza antes de abrir uma nova negociação para que a exposição líquida nunca misture posições longas e curtas.
Sinais Reversos – troca os gatilhos de compra e venda, transformando a estratégia em uma versão contrária ou que segue a tendência.
Tipo de vela – período de tempo das velas utilizadas para avaliação do indicador e sinal.
Lógica de negociação
Uma assinatura de vela é aberta para o período selecionado e alimentada em um indicador DeMarker.
Quando a última vela finalizada fecha, o valor atual do DeMarker é comparado com os níveis configurados.
Sem reversão:
Se o DeMarker estiver abaixo do nível inferior, a estratégia tenta construir ou aumentar uma posição longa.
Se o DeMarker estiver acima do nível superior, a estratégia tenta construir ou aumentar uma posição curta.
Com a reversão habilitada, o significado dos níveis é invertido (mínimos extremos acionam posições vendidas e altas extremas acionam posições compradas).
O algoritmo lembra o horário da última negociação executada para evitar múltiplas entradas na mesma vela.
Gerenciamento de posição
Antes de mudar de direção, a estratégia verifica o lucro não realizado da posição existente. A exposição oposta é fechada somente se o preço atual da vela sair da negociação com um resultado positivo, refletindo o comportamento de proteção do EA original.
As médias de posição são rastreadas internamente. Quando pedidos adicionais são adicionados na mesma direção, o preço médio é recalculado para avaliar corretamente a lucratividade.
O parâmetro opcional Only One Position força um estado estável antes de entrar em uma nova negociação, o que é útil ao executar no modo de posição líquida.
StartProtection() é invocado assim que a estratégia começa para garantir que a liquidação de emergência permaneça disponível se a posição se tornar diferente de zero e o algoritmo parar.
Notas
A conversão foi projetada para StockSharp API de alto nível e não depende de nenhuma coleção personalizada ou pesquisa direta de valor do indicador.
Os modelos de dimensionamento de risco da versão MetaTrader (margem fixa, risco percentual, etc.) são intencionalmente simplificados para o parâmetro constante Trade Volume. Ajuste o dimensionamento da posição externamente se for necessário controle dinâmico de risco.
Como os preenchimentos são simulados com ordens de mercado a preços de fechamento de velas, lembre-se de validar a configuração em relação à execução real do corretor e aos requisitos de deslizamento.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Uses the DeMarker oscillator to accumulate positions when extreme levels are reached.
/// </summary>
public class DeMarkerGainingPositionVolumeStrategy : Strategy
{
private readonly StrategyParam<int> _deMarkerPeriod;
private readonly StrategyParam<decimal> _upperLevel;
private readonly StrategyParam<decimal> _lowerLevel;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private decimal? _prevOscillator;
/// <summary>
/// Number of candles used by the DeMarker indicator.
/// </summary>
public int DeMarkerPeriod
{
get => _deMarkerPeriod.Value;
set => _deMarkerPeriod.Value = value;
}
/// <summary>
/// DeMarker level that triggers short entries.
/// </summary>
public decimal UpperLevel
{
get => _upperLevel.Value;
set => _upperLevel.Value = value;
}
/// <summary>
/// DeMarker level that triggers long entries.
/// </summary>
public decimal LowerLevel
{
get => _lowerLevel.Value;
set => _lowerLevel.Value = value;
}
/// <summary>
/// Candle type that defines the timeframe for the oscillator.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public DeMarkerGainingPositionVolumeStrategy()
{
_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 14)
.SetDisplay("DeMarker Period", "Number of bars used by the oscillator.", "Indicator");
_upperLevel = Param(nameof(UpperLevel), 0.7m)
.SetDisplay("Upper Level", "Threshold that prepares short exposure.", "Indicator");
_lowerLevel = Param(nameof(LowerLevel), 0.3m)
.SetDisplay("Lower Level", "Threshold that prepares long exposure.", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for DeMarker calculations.", "Data");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevOscillator = null;
_rsi = new RelativeStrengthIndex { Length = DeMarkerPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed)
{
_prevOscillator = rsiValue / 100m;
return;
}
var oscillatorValue = rsiValue / 100m;
if (_prevOscillator is null)
{
_prevOscillator = oscillatorValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Oscillator crosses below the lower level => oversold => buy
if (_prevOscillator > LowerLevel && oscillatorValue <= LowerLevel)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
// Oscillator crosses above the upper level => overbought => sell
else if (_prevOscillator < UpperLevel && oscillatorValue >= UpperLevel)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevOscillator = oscillatorValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_rsi = null;
_prevOscillator = null;
base.OnReseted();
}
}