A Estratégia E-News Lucky é um port do StockSharp do consultor especialista MetaTrader e-News-Lucky. O sistema automatiza a abordagem clássica de rompimento por notícias:
Em um PlacementTime configurável, envia tanto ordens de compra stop quanto de venda stop ao redor do preço atual, deslocadas por DistancePips.
Quando qualquer ordem pendente é executada, a ordem oposta é cancelada imediatamente. Níveis iniciais de stop-loss e take-profit de proteção são anexados de acordo com os deslocamentos em pips configurados.
Um trailing stop pode ser habilitado via TrailingStopPips e TrailingStepPips para travar lucros à medida que a negociação se move na direção favorável.
No CancelTime configurado, todas as ordens pendentes restantes são removidas e quaisquer posições abertas são fechadas para evitar manter risco fora da janela de negociação.
A estratégia usa dados de candles (CandleType, 1 minuto por padrão) apenas para rastrear os horários agendados e atualizar o trailing stop. Ela não depende de cálculos de indicadores.
Parâmetros
Nome
Descrição
Volume
Volume de ordem para cada entrada pendente. A estratégia envia ordens simétricas de compra stop e venda stop com este volume.
StopLossPips
Distância entre o preço de entrada e o stop-loss de proteção, expressa em pips. Definir como zero para desabilitar o stop.
TakeProfitPips
Distância entre o preço de entrada e o alvo de lucro em pips. Definir como zero para desabilitar o alvo.
TrailingStopPips
Distância do trailing stop em pips. O motor de trailing fica ativo somente quando este valor é maior que zero.
TrailingStepPips
Ganho mínimo em pips necessário antes que o trailing stop seja movido novamente. Previne atualizações excessivas do stop em mercados laterais.
DistancePips
Deslocamento (em pips) do preço atual usado para colocar as ordens stop.
PlacementTime
Hora do dia (tempo do broker/servidor) quando as ordens pendentes são colocadas. Padrão: 10:30.
CancelTime
Hora do dia quando as ordens pendentes são canceladas e as posições abertas são fechadas. Padrão: 22:30.
CandleType
Série de candles usada para agendamento e trailing. Padrão: período de 1 minuto.
Notas de implementação
O tamanho de pip segue a lógica do MetaTrader: se o símbolo tem 3 ou 5 dígitos, a estratégia multiplica o passo de preço por 10 para trabalhar em unidades de pip.
Todos os preços são normalizados para o passo de preço do instrumento antes de as ordens serem enviadas.
Os trailing stops comparam o último fechamento contra PositionPrice e apenas movem o stop de proteção quando o ganho excede tanto TrailingStopPips quanto TrailingStepPips.
As ordens pendentes são recriadas cada dia de negociação quando o tempo de colocação é alcançado. As verificações do tempo de cancelamento garantem que toda a exposição esteja zerada ao final da janela.
Dicas de uso
Anexe a estratégia a um instrumento líquido com spreads apertados; as distâncias de rompimento assumem comportamento de preço semelhante a notícias.
Defina PlacementTime e CancelTime de acordo com o calendário econômico de interesse.
Ajuste as distâncias em pips para corresponder à volatilidade do instrumento. Valores maiores reduzem a chance de falsos disparos, enquanto valores menores podem capturar movimentos mais cedo, mas aumentam o risco de whipsaw.
Desabilite o trailing mantendo TrailingStopPips em zero se stops fixos forem preferidos.
Monitore o slippage e o spread durante notícias de alto impacto para garantir que as ordens pendentes sejam executadas conforme esperado.
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>
/// Scheduled breakout strategy that monitors price around a reference level and enters on breakout.
/// Converted from the original pending-order version to use market orders.
/// </summary>
public class ENewsLuckyStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _trailingStepPips;
private readonly StrategyParam<decimal> _distancePips;
private readonly StrategyParam<int> _placementHour;
private readonly StrategyParam<int> _cancelHour;
private readonly StrategyParam<DataType> _candleType;
private decimal _pipSize;
private decimal? _buyLevel;
private decimal? _sellLevel;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private bool _pendingActive;
private bool _lastWasPlacementDay;
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
public decimal TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
public decimal DistancePips
{
get => _distancePips.Value;
set => _distancePips.Value = value;
}
public int PlacementHour
{
get => _placementHour.Value;
set => _placementHour.Value = value;
}
public int CancelHour
{
get => _cancelHour.Value;
set => _cancelHour.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public ENewsLuckyStrategy()
{
_stopLossPips = Param(nameof(StopLossPips), 50m)
.SetDisplay("Stop Loss", "Stop loss in pips", "Trading");
_takeProfitPips = Param(nameof(TakeProfitPips), 150m)
.SetDisplay("Take Profit", "Take profit in pips", "Trading");
_trailingStopPips = Param(nameof(TrailingStopPips), 5m)
.SetDisplay("Trailing Stop", "Trailing distance in pips", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
.SetDisplay("Trailing Step", "Minimum trailing step in pips", "Risk");
_distancePips = Param(nameof(DistancePips), 20m)
.SetGreaterThanZero()
.SetDisplay("Entry Distance", "Distance from market in pips", "Trading");
_placementHour = Param(nameof(PlacementHour), 2)
.SetDisplay("Placement Hour", "Hour to set breakout levels", "General");
_cancelHour = Param(nameof(CancelHour), 22)
.SetDisplay("Cancel Hour", "Hour to cancel and close", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Working candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
_buyLevel = null;
_sellLevel = null;
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
_pendingActive = false;
_lastWasPlacementDay = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var step = Security?.PriceStep ?? 0m;
_pipSize = step > 0 ? step : 1m;
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var hour = candle.CloseTime.Hour;
var price = candle.ClosePrice;
// Set breakout levels at placement hour
if (hour == PlacementHour && !_lastWasPlacementDay && Position == 0)
{
var distance = DistancePips * _pipSize;
_buyLevel = price + distance;
_sellLevel = price - distance;
_pendingActive = true;
_lastWasPlacementDay = true;
}
if (hour != PlacementHour)
_lastWasPlacementDay = false;
// Cancel at cancel hour
if (hour == CancelHour && _pendingActive)
{
_pendingActive = false;
_buyLevel = null;
_sellLevel = null;
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
return;
}
// Check breakout triggers
if (_pendingActive && Position == 0)
{
if (_buyLevel.HasValue && candle.HighPrice >= _buyLevel.Value)
{
var buyLevel = _buyLevel.Value;
BuyMarket(Volume);
_entryPrice = buyLevel;
_stopPrice = StopLossPips > 0 ? _entryPrice - StopLossPips * _pipSize : null;
_takePrice = TakeProfitPips > 0 ? _entryPrice + TakeProfitPips * _pipSize : null;
_pendingActive = false;
_buyLevel = null;
_sellLevel = null;
}
else if (_sellLevel.HasValue && candle.LowPrice <= _sellLevel.Value)
{
var sellLevel = _sellLevel.Value;
SellMarket(Volume);
_entryPrice = sellLevel;
_stopPrice = StopLossPips > 0 ? _entryPrice + StopLossPips * _pipSize : null;
_takePrice = TakeProfitPips > 0 ? _entryPrice - TakeProfitPips * _pipSize : null;
_pendingActive = false;
_buyLevel = null;
_sellLevel = null;
}
}
// Manage open position
if (Position > 0)
{
// Trailing stop
if (TrailingStopPips > 0 && _entryPrice > 0)
{
var trailDist = TrailingStopPips * _pipSize;
var stepDist = TrailingStepPips * _pipSize;
if (price - _entryPrice > trailDist + stepDist)
{
var newStop = price - trailDist;
if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
_stopPrice = newStop;
}
}
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Position);
ResetPosition();
return;
}
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
SellMarket(Position);
ResetPosition();
}
}
else if (Position < 0)
{
// Trailing stop
if (TrailingStopPips > 0 && _entryPrice > 0)
{
var trailDist = TrailingStopPips * _pipSize;
var stepDist = TrailingStepPips * _pipSize;
if (_entryPrice - price > trailDist + stepDist)
{
var newStop = price + trailDist;
if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
_stopPrice = newStop;
}
}
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(-Position);
ResetPosition();
return;
}
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
BuyMarket(-Position);
ResetPosition();
}
}
}
private void ResetPosition()
{
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
}
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 e_news_lucky_strategy(Strategy):
def __init__(self):
super(e_news_lucky_strategy, self).__init__()
self._stop_loss_pips = self.Param("StopLossPips", 50.0)
self._take_profit_pips = self.Param("TakeProfitPips", 150.0)
self._trailing_stop_pips = self.Param("TrailingStopPips", 5.0)
self._trailing_step_pips = self.Param("TrailingStepPips", 5.0)
self._distance_pips = self.Param("DistancePips", 20.0)
self._placement_hour = self.Param("PlacementHour", 2)
self._cancel_hour = self.Param("CancelHour", 22)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._pip_size = 0.0
self._buy_level = None
self._sell_level = None
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._pending_active = False
self._last_was_placement_day = False
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(e_news_lucky_strategy, self).OnStarted2(time)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0
self._pip_size = step if step > 0 else 1.0
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
hour = candle.CloseTime.Hour
price = float(candle.ClosePrice)
# Set breakout levels at placement hour
if hour == self._placement_hour.Value and not self._last_was_placement_day and self.Position == 0:
distance = self._distance_pips.Value * self._pip_size
self._buy_level = price + distance
self._sell_level = price - distance
self._pending_active = True
self._last_was_placement_day = True
if hour != self._placement_hour.Value:
self._last_was_placement_day = False
# Cancel at cancel hour
if hour == self._cancel_hour.Value and self._pending_active:
self._pending_active = False
self._buy_level = None
self._sell_level = None
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
return
# Check breakout triggers
if self._pending_active and self.Position == 0:
if self._buy_level is not None and float(candle.HighPrice) >= self._buy_level:
buy_level = self._buy_level
self.BuyMarket(self.Volume)
self._entry_price = buy_level
self._stop_price = self._entry_price - self._stop_loss_pips.Value * self._pip_size if self._stop_loss_pips.Value > 0 else None
self._take_price = self._entry_price + self._take_profit_pips.Value * self._pip_size if self._take_profit_pips.Value > 0 else None
self._pending_active = False
self._buy_level = None
self._sell_level = None
elif self._sell_level is not None and float(candle.LowPrice) <= self._sell_level:
sell_level = self._sell_level
self.SellMarket(self.Volume)
self._entry_price = sell_level
self._stop_price = self._entry_price + self._stop_loss_pips.Value * self._pip_size if self._stop_loss_pips.Value > 0 else None
self._take_price = self._entry_price - self._take_profit_pips.Value * self._pip_size if self._take_profit_pips.Value > 0 else None
self._pending_active = False
self._buy_level = None
self._sell_level = None
# Manage open position
if self.Position > 0:
# Trailing stop for long
if self._trailing_stop_pips.Value > 0 and self._entry_price > 0:
trail_dist = self._trailing_stop_pips.Value * self._pip_size
step_dist = self._trailing_step_pips.Value * self._pip_size
if price - self._entry_price > trail_dist + step_dist:
new_stop = price - trail_dist
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(self.Position)
self._reset_position()
return
if self._take_price is not None and float(candle.HighPrice) >= self._take_price:
self.SellMarket(self.Position)
self._reset_position()
elif self.Position < 0:
# Trailing stop for short
if self._trailing_stop_pips.Value > 0 and self._entry_price > 0:
trail_dist = self._trailing_stop_pips.Value * self._pip_size
step_dist = self._trailing_step_pips.Value * self._pip_size
if self._entry_price - price > trail_dist + step_dist:
new_stop = price + trail_dist
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_position()
return
if self._take_price is not None and float(candle.LowPrice) <= self._take_price:
self.BuyMarket(abs(self.Position))
self._reset_position()
def _reset_position(self):
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(e_news_lucky_strategy, self).OnReseted()
self._pip_size = 0.0
self._buy_level = None
self._sell_level = None
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._pending_active = False
self._last_was_placement_day = False
def CreateClone(self):
return e_news_lucky_strategy()