E-News Lucky 策略
概览
E-News Lucky 策略是 MetaTrader 智能交易系统 e-News-Lucky 的 StockSharp 版本。策略实现了典型的新闻突破思路:
- 在设定的
PlacementTime时间点,策略会在当前价格上下按照DistancePips的距离分别挂出 Buy Stop 与 Sell Stop 订单。 - 任一方向触发成交后,会立即撤销另一侧的挂单,并根据参数设置自动创建止损与止盈价位。
- 通过
TrailingStopPips与TrailingStepPips可以启用移动止损,在行情顺势运行时逐步锁定利润。 - 到达
CancelTime时,策略会撤销所有剩余挂单,并平掉所有持仓,以避免在交易窗口之外暴露风险。
策略仅使用蜡烛图数据(CandleType,默认 1 分钟)来判断时间与更新移动止损,不依赖任何指标。
参数说明
| 参数 | 说明 |
|---|---|
Volume |
每个挂单的下单手数。策略会同时放置对称的 Buy Stop 与 Sell Stop。 |
StopLossPips |
入场价与止损之间的距离(单位:点)。设置为 0 表示不使用止损。 |
TakeProfitPips |
入场价与止盈之间的距离(单位:点)。设置为 0 表示不使用止盈。 |
TrailingStopPips |
移动止损的基础距离(单位:点)。大于 0 时才会启动移动止损。 |
TrailingStepPips |
每次调整移动止损所需的最小获利(单位:点),可减少震荡行情下的频繁移动。 |
DistancePips |
挂单距离当前价格的偏移量(单位:点)。 |
PlacementTime |
提交挂单的服务器时间。默认值:10:30。 |
CancelTime |
撤单并平仓的服务器时间。默认值:22:30。 |
CandleType |
用于调度与移动止损的蜡烛类型。默认值:1 分钟。 |
实现细节
- 点值计算与原始 EA 保持一致:若品种报价保留 3 位或 5 位小数,则将最小价格步长乘以 10 作为点值。
- 所有下单价格都会先按品种的最小价格变动单位进行规范化。
- 移动止损会比较最新收盘价与
PositionPrice,只有当浮盈超过TrailingStopPips与TrailingStepPips时才会上移(或下移)止损。 - 每个交易日到达
PlacementTime会重新挂出双向突破单,到达CancelTime则保证所有订单与仓位被清理。
使用建议
- 选择流动性高、点差小的品种运行本策略,以适应新闻行情的快速波动。
- 根据关注的经济事件调整
PlacementTime与CancelTime,确保在新闻公布前后执行策略。 - 依据市场波动率调整各类点数参数:较大的距离可减少假突破,较小的距离可以更早入场但风险更高。
- 如需固定止损,可将
TrailingStopPips设为 0 以关闭移动止损功能。 - 在重大事件期间注意观察滑点与点差变化,确保挂单能按预期成交。
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()