A estratégia de canal equidistante porta o expert advisor MQL4 original "Equidistant Channel" para a API de alto nível do StockSharp. A estratégia analisa cruzamentos da linha MACD e gerencia posições existentes por toques nas Bollinger Bands, lógica de breakeven e alvos de trailing baseados em dinheiro.
Quando a linha MACD cruza acima do seu sinal, a estratégia abre posições compradas; quando cruza abaixo do sinal, abre posições vendidas. Enquanto uma operação está ativa, a estratégia observa saídas quando o preço alcança Bollinger Bands, quando o lucro flutuante atinge alvos monetários ou percentuais configuráveis, ou quando um limite de drawdown trailing é violado. Um modo breakeven espelha a implementação do MetaTrader movendo o stop de proteção quando o lucro excede um número configurável de passos de preço.
Indicadores
MACD (12, 26, 9) - gera sinais de entrada em cruzamentos entre a linha MACD e sua linha de sinal.
Bollinger Bands (20, 2) - fornecem níveis de saída sempre que o fechamento do candle atinge a banda superior ou inferior.
Gestão da posição
Distâncias opcionais de stop loss, take profit e trailing stop expressas em pontos de preço via StartProtection.
Lógica de take profit e trailing baseada em dinheiro que acompanha o lucro flutuante usando metadados de preço/tamanho do passo do instrumento.
Take profit percentual calculado a partir do valor inicial do portfólio.
Modo breakeven que empurra o stop para a entrada mais um offset quando o lucro alcança um gatilho definido.
Parâmetros
Grupo
Nome
Padrão
Descrição
Negociação
Volume
1
Volume da ordem para novas entradas.
Geral
Tipo de candle
5 minutos
Série de candles usada para os cálculos.
Indicadores
MACD rápido
12
Comprimento da EMA rápida para MACD.
Indicadores
MACD lento
26
Comprimento da EMA lenta para MACD.
Indicadores
Sinal MACD
9
Comprimento da linha de sinal para MACD.
Indicadores
Período BB
20
Período de retrospectiva das Bollinger Bands.
Indicadores
Desvio BB
2
Largura das Bollinger Bands em desvios padrão.
Risco
Stop Loss
20
Distância do stop loss em pontos de preço.
Risco
Take Profit
50
Distância do take profit em pontos de preço.
Risco
Trailing Stop
40
Distância do trailing stop em pontos de preço.
Risco
Usar TP (dinheiro)
false
Fecha quando o lucro flutuante atinge um alvo monetário absoluto.
Risco
Dinheiro TP
10
Valor absoluto de take profit na moeda da conta.
Risco
Usar TP (%)
false
Fecha quando o lucro flutuante atinge um percentual do capital inicial.
Risco
Percentual TP
10
Percentual do capital inicial para o take profit percentual.
Risco
Habilitar trailing
true
Habilita a lógica de trailing sobre o lucro flutuante.
Risco
Ativar trailing
40
Nível de lucro (moeda) que arma a lógica de trailing.
Risco
Passo trailing
10
Drawdown máximo permitido a partir do pico de lucro (moeda).
Risco
Usar stop BB
true
Habilita saídas quando o preço toca Bollinger Bands.
Risco
Usar breakeven
true
Habilita o comportamento breakeven.
Risco
Gatilho breakeven
10
Lucro (passos de preço) necessário para armar o stop breakeven.
Risco
Offset breakeven
5
Offset (passos de preço) aplicado ao nível breakeven.
Observações
A estratégia funciona com um único instrumento que forneça metadados válidos de PriceStep e StepPrice, para que os cálculos monetários sejam precisos.
O módulo de trailing de lucro segue o comportamento do MetaTrader: quando o lucro flutuante excede o limite de ativação, a estratégia registra o máximo corrente e fecha a operação quando o drawdown excede o passo trailing configurado.
A lógica breakeven espelha o EA original usando gatilhos e offsets baseados em passos de preço.
Todos os comentários dentro do código da estratégia são escritos em inglês, conforme exigido pelas diretrizes do projeto.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class EquidistantChannelStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public EquidistantChannelStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class equidistant_channel_strategy(Strategy):
def __init__(self):
super(equidistant_channel_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(equidistant_channel_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(equidistant_channel_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return equidistant_channel_strategy()