A estratégia de Setas Doji converte o expert advisor original "Doji Arrows" do MetaTrader para a API de alto nível do StockSharp. A ideia é aguardar uma vela doji genuína e então operar um rompimento do seu range. Uma vela doji representa indecisão, portanto, um fechamento acima da máxima do doji indica força altista enquanto um fechamento abaixo da mínima do doji indica controle baixista.
A estratégia processa apenas velas completadas da assinatura CandleType configurada.
A vela anterior é analisada para determinar se é um doji. A vela é classificada como doji quando a diferença absoluta entre a abertura e o fechamento é menor ou igual a DojiBodyPoints multiplicado pelo passo de preço do instrumento. Se o parâmetro for definido como 0, um único passo de preço é usado como tolerância, o que corresponde à verificação de igualdade estrita na versão MQL5.
Quando a próxima vela fecha acima da máxima do doji, a estratégia envia uma ordem de compra a mercado. Quando a próxima vela fecha abaixo da mínima do doji, uma ordem de venda a mercado é emitida. Posições opostas existentes são niveladas automaticamente pelo volume da ordem de mercado.
Esta sequência espelha o expert advisor original que reagia uma vez na abertura de cada nova barra.
Gerenciamento de Risco
A implementação convertida mantém o comportamento protetor do script MQL:
Stop loss: StopLossPoints controla quão longe, em passos de preço, o stop loss inicial é colocado do preço de entrada. Defina como zero para desabilitar o stop fixo.
Take profit: TakeProfitPoints define a distância até o objetivo de lucro em passos de preço. Defina como zero para pular o objetivo.
Trailing stop: TrailingStopPoints e TrailingStepPoints reproduzem o mecanismo de trailing. Uma vez que a operação ganha mais de TrailingStopPoints + TrailingStepPoints, o nível de stop é puxado para TrailingStopPoints do último fechamento (fechamento mais alto para comprado, fechamento mais baixo para vendido). O trailing é opcional e ativa apenas quando TrailingStopPoints é maior que zero.
Stops e objetivos são avaliados em cada vela terminada. Quando qualquer nível é violado (usando a máxima/mínima da vela), a estratégia sai da posição com uma ordem de mercado e reinicia o estado de proteção.
Parâmetros
Parâmetro
Padrão
Descrição
StopLossPoints
30
Distância do stop loss inicial em passos de preço.
TakeProfitPoints
90
Distância do take profit em passos de preço.
TrailingStopPoints
15
Distância usada pelo trailing stop em passos de preço.
TrailingStepPoints
5
Lucro adicional necessário antes de ajustar o trailing stop, em passos de preço.
DojiBodyPoints
1
Tamanho máximo permitido do corpo da vela anterior em passos de preço para tratá-la como um doji. 0 usa um passo de preço como tolerância.
CandleType
1 hora
Tipo de vela assinada para geração de sinais.
Notas de Implementação
A estratégia assina velas através de SubscribeCandles(CandleType).Bind(ProcessCandle) e mantém apenas a última vela completada na memória.
O passo de preço do instrumento é obtido via Security?.PriceStep. Quando não disponível, um valor de fallback de 1 é usado para que a estratégia possa operar igualmente com dados sintéticos ou históricos.
Os níveis protetores são recalculados após cada entrada, e a lógica de trailing pode criar um stop mesmo quando o stop loss fixo está desabilitado (correspondendo ao comportamento MQL onde o trailing stop podia começar de zero).
Todas as ações são executadas com ordens de mercado para manter alinhamento com o advisor original que dependia de execução imediata no mercado.
Dicas de Uso
Configure as propriedades Security, Portfolio e Volume antes de iniciar a estratégia.
Ajuste os parâmetros baseados em pontos de acordo com o instrumento operado. Para instrumentos cotados com pips fracionários, aumente os valores para corresponder ao tamanho do tick do broker.
Combine a estratégia com controles de risco ou módulos de análise do StockSharp se for necessário dimensionamento de posição mais avançado, pois a conversão mantém a lógica de volume fixo do código original.
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>
/// Doji breakout strategy with optional fixed and trailing protection.
/// </summary>
public class DojiArrowsStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<decimal> _dojiBodyPoints;
private readonly StrategyParam<DataType> _candleType;
private bool _hasPreviousCandle;
private decimal _prevOpen;
private decimal _prevClose;
private decimal _prevHigh;
private decimal _prevLow;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
public decimal TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
public decimal DojiBodyPoints
{
get => _dojiBodyPoints.Value;
set => _dojiBodyPoints.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public DojiArrowsStrategy()
{
_stopLossPoints = Param(nameof(StopLossPoints), 30m)
.SetNotNegative()
.SetDisplay("Stop Loss Points", "Stop loss distance in price steps.", "Risk")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 90m)
.SetNotNegative()
.SetDisplay("Take Profit Points", "Take profit distance in price steps.", "Risk")
;
_trailingStopPoints = Param(nameof(TrailingStopPoints), 15m)
.SetNotNegative()
.SetDisplay("Trailing Stop Points", "Trailing distance in price steps.", "Risk")
;
_trailingStepPoints = Param(nameof(TrailingStepPoints), 5m)
.SetNotNegative()
.SetDisplay("Trailing Step Points", "Minimum profit before the trailing stop moves.", "Risk")
;
_dojiBodyPoints = Param(nameof(DojiBodyPoints), 1m)
.SetNotNegative()
.SetDisplay("Doji Body Points", "Maximum difference between open and close to treat the candle as a doji.", "Pattern")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Time frame used for signal generation.", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_hasPreviousCandle = false;
_prevOpen = 0m;
_prevClose = 0m;
_prevHigh = 0m;
_prevLow = 0m;
ResetProtection();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
ManageActivePosition(candle);
if (!_hasPreviousCandle)
{
CachePreviousCandle(candle);
return;
}
var step = Security?.PriceStep ?? 1m;
var tolerance = DojiBodyPoints <= 0m ? step : DojiBodyPoints * step;
var bodySize = Math.Abs(_prevOpen - _prevClose);
var isDoji = bodySize <= tolerance;
var breakoutUp = isDoji && candle.ClosePrice > _prevHigh;
var breakoutDown = isDoji && candle.ClosePrice < _prevLow;
if (breakoutUp && Position == 0)
{
BuyMarket();
}
else if (breakoutDown && Position == 0)
{
SellMarket();
}
CachePreviousCandle(candle);
}
private void ManageActivePosition(ICandleMessage candle)
{
if (Position == 0)
return;
var step = Security?.PriceStep ?? 1m;
var trailingDistance = TrailingStopPoints > 0m ? TrailingStopPoints * step : 0m;
var trailingStep = TrailingStepPoints > 0m ? TrailingStepPoints * step : 0m;
if (Position > 0)
{
if (trailingDistance > 0m && _entryPrice.HasValue)
{
var gain = candle.ClosePrice - _entryPrice.Value;
if (gain > trailingDistance + trailingStep)
{
var newStop = candle.ClosePrice - trailingDistance;
if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
_stopPrice = newStop;
}
}
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Math.Abs(Position));
ResetProtection();
return;
}
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
SellMarket(Math.Abs(Position));
ResetProtection();
return;
}
}
else if (Position < 0)
{
if (trailingDistance > 0m && _entryPrice.HasValue)
{
var gain = _entryPrice.Value - candle.ClosePrice;
if (gain > trailingDistance + trailingStep)
{
var newStop = candle.ClosePrice + trailingDistance;
if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
_stopPrice = newStop;
}
}
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetProtection();
return;
}
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetProtection();
return;
}
}
}
private void InitializeProtection(decimal price, bool isLong, decimal step)
{
_entryPrice = price;
if (StopLossPoints > 0m)
{
var offset = StopLossPoints * step;
_stopPrice = isLong ? price - offset : price + offset;
}
else
{
_stopPrice = null;
}
if (TakeProfitPoints > 0m)
{
var offset = TakeProfitPoints * step;
_takePrice = isLong ? price + offset : price - offset;
}
else
{
_takePrice = null;
}
}
private void ResetProtection()
{
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
}
private void CachePreviousCandle(ICandleMessage candle)
{
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_hasPreviousCandle = true;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
class doji_arrows_strategy(Strategy):
def __init__(self):
super(doji_arrows_strategy, self).__init__()
self._stop_loss_points = self.Param("StopLossPoints", 30.0)
self._take_profit_points = self.Param("TakeProfitPoints", 90.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 15.0)
self._trailing_step_points = self.Param("TrailingStepPoints", 5.0)
self._doji_body_points = self.Param("DojiBodyPoints", 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._has_previous_candle = False
self._prev_open = 0.0
self._prev_close = 0.0
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = None
self._stop_price = None
self._take_price = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(doji_arrows_strategy, self).OnStarted2(time)
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._manage_active_position(candle)
if not self._has_previous_candle:
self._cache_previous_candle(candle)
return
step = self._get_price_step()
tolerance = step if self._doji_body_points.Value <= 0 else self._doji_body_points.Value * step
body_size = abs(self._prev_open - self._prev_close)
is_doji = body_size <= tolerance
breakout_up = is_doji and float(candle.ClosePrice) > self._prev_high
breakout_down = is_doji and float(candle.ClosePrice) < self._prev_low
if breakout_up and self.Position == 0:
self.BuyMarket()
self._initialize_protection(float(candle.ClosePrice), True, step)
elif breakout_down and self.Position == 0:
self.SellMarket()
self._initialize_protection(float(candle.ClosePrice), False, step)
self._cache_previous_candle(candle)
def _manage_active_position(self, candle):
if self.Position == 0:
return
step = self._get_price_step()
trailing_distance = self._trailing_stop_points.Value * step if self._trailing_stop_points.Value > 0 else 0.0
trailing_step = self._trailing_step_points.Value * step if self._trailing_step_points.Value > 0 else 0.0
if self.Position > 0:
if trailing_distance > 0 and self._entry_price is not None:
gain = float(candle.ClosePrice) - self._entry_price
if gain > trailing_distance + trailing_step:
new_stop = float(candle.ClosePrice) - trailing_distance
if self._stop_price is None or new_stop > self._stop_price:
self._stop_price = new_stop
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(abs(self.Position))
self._reset_protection()
return
if self._take_price is not None and float(candle.HighPrice) >= self._take_price:
self.SellMarket(abs(self.Position))
self._reset_protection()
return
elif self.Position < 0:
if trailing_distance > 0 and self._entry_price is not None:
gain = self._entry_price - float(candle.ClosePrice)
if gain > trailing_distance + trailing_step:
new_stop = float(candle.ClosePrice) + trailing_distance
if self._stop_price is None or new_stop < self._stop_price:
self._stop_price = new_stop
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(abs(self.Position))
self._reset_protection()
return
if self._take_price is not None and float(candle.LowPrice) <= self._take_price:
self.BuyMarket(abs(self.Position))
self._reset_protection()
return
def _initialize_protection(self, price, is_long, step):
self._entry_price = price
if self._stop_loss_points.Value > 0:
offset = self._stop_loss_points.Value * step
self._stop_price = price - offset if is_long else price + offset
else:
self._stop_price = None
if self._take_profit_points.Value > 0:
offset = self._take_profit_points.Value * step
self._take_price = price + offset if is_long else price - offset
else:
self._take_price = None
def _reset_protection(self):
self._entry_price = None
self._stop_price = None
self._take_price = None
def _cache_previous_candle(self, candle):
self._prev_open = float(candle.OpenPrice)
self._prev_close = float(candle.ClosePrice)
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._has_previous_candle = True
def _get_price_step(self):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
return step if step > 0 else 1.0
def OnReseted(self):
super(doji_arrows_strategy, self).OnReseted()
self._has_previous_candle = False
self._prev_open = 0.0
self._prev_close = 0.0
self._prev_high = 0.0
self._prev_low = 0.0
self._reset_protection()
def CreateClone(self):
return doji_arrows_strategy()