PEAD Strategy
该策略在盈利公告后出现正向EPS惊喜且跳空上涨时交易。 当价格在业绩公布后的第二天跳空上涨且近期表现良好时进场, 使用EMA追踪、固定止损/保本,以及持仓条数上限。
细节
- 入场条件:EPS正向惊喜、业绩后跳空上涨,并且近期表现为正。
- 多空方向:仅做多。
- 出场条件:日线EMA下穿、固定止损/保本或持仓达到最大条数。
- 止损:固定止损并可移动至保本。
- 默认值:
GapThreshold= 1EpsSurpriseThreshold= 5PerfDays= 20StopPct= 8EmaLen= 50MaxHoldBars= 50CandleType= TimeSpan.FromDays(1)
- 过滤器:
- 类别: Earnings
- 方向: Long
- 指标: EMA
- 止损: 是
- 复杂度: Intermediate
- 时间框架: Daily
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: Medium
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>
/// Post-earnings announcement drift strategy with gap and EMA exit.
/// </summary>
public class PeadStrategy : Strategy
{
private readonly StrategyParam<decimal> _gapThreshold;
private readonly StrategyParam<decimal> _epsSurpriseThreshold;
private readonly StrategyParam<int> _perfDays;
private readonly StrategyParam<decimal> _stopPct;
private readonly StrategyParam<int> _emaLen;
private readonly StrategyParam<int> _maxHoldBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevEma;
private decimal _prevRoc;
/// <summary>
/// Gap-up threshold (%).
/// </summary>
public decimal GapThreshold
{
get => _gapThreshold.Value;
set => _gapThreshold.Value = value;
}
/// <summary>
/// EPS surprise threshold (%).
/// </summary>
public decimal EpsSurpriseThreshold
{
get => _epsSurpriseThreshold.Value;
set => _epsSurpriseThreshold.Value = value;
}
/// <summary>
/// Positive-performance look-back.
/// </summary>
public int PerfDays
{
get => _perfDays.Value;
set => _perfDays.Value = value;
}
/// <summary>
/// Initial fixed stop-loss (%).
/// </summary>
public decimal StopPct
{
get => _stopPct.Value;
set => _stopPct.Value = value;
}
/// <summary>
/// Daily EMA length for trail stop.
/// </summary>
public int EmaLen
{
get => _emaLen.Value;
set => _emaLen.Value = value;
}
/// <summary>
/// Exit after N bars from entry.
/// </summary>
public int MaxHoldBars
{
get => _maxHoldBars.Value;
set => _maxHoldBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public PeadStrategy()
{
_gapThreshold = Param(nameof(GapThreshold), 1m)
.SetDisplay("Gap Threshold", "Gap-up threshold (%)", "General")
;
_epsSurpriseThreshold = Param(nameof(EpsSurpriseThreshold), 5m)
.SetDisplay("EPS Surprise", "EPS surprise threshold (%)", "General")
;
_perfDays = Param(nameof(PerfDays), 20)
.SetDisplay("Performance Days", "Positive-performance look-back", "General")
;
_stopPct = Param(nameof(StopPct), 8m)
.SetDisplay("Stop Percent", "Initial fixed stop-loss (%)", "Risk")
;
_emaLen = Param(nameof(EmaLen), 50)
.SetDisplay("EMA Length", "Daily EMA length for trail stop", "Indicators")
;
_maxHoldBars = Param(nameof(MaxHoldBars), 50)
.SetDisplay("Max Hold Bars", "Exit after N bars from entry", "General")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0;
_prevEma = 0;
_prevRoc = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaLen };
var roc = new RateOfChange { Length = PerfDays + 1 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, roc, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(3, UnitTypes.Percent),
stopLoss: new Unit(StopPct, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawIndicator(area, roc);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal rocValue)
{
if (candle.State != CandleStates.Finished)
return;
// Use strong candle body instead of gap (gaps don't exist in continuous 5min data)
var bodyPct = _prevClose != 0 ? Math.Abs(candle.ClosePrice - candle.OpenPrice) / _prevClose * 100m : 0m;
var strongMove = bodyPct >= GapThreshold;
var perfPos = _prevRoc > 0;
var entryCond = perfPos && strongMove && Position == 0;
if (entryCond)
{
BuyMarket();
}
_prevClose = candle.ClosePrice;
_prevEma = emaValue;
_prevRoc = rocValue;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RateOfChange
from StockSharp.Algo.Strategies import Strategy
class pead_strategy(Strategy):
def __init__(self):
super(pead_strategy, self).__init__()
self._gap_threshold = self.Param("GapThreshold", 1.0)
self._perf_days = self.Param("PerfDays", 20)
self._stop_pct = self.Param("StopPct", 8.0)
self._ema_len = self.Param("EmaLen", 50)
self._max_hold_bars = self.Param("MaxHoldBars", 50)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._prev_close = 0.0
self._prev_roc = 0.0
self._entry_price = 0.0
self._bars_in_trade = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(pead_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_roc = 0.0
self._entry_price = 0.0
self._bars_in_trade = 0
def OnStarted2(self, time):
super(pead_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_roc = 0.0
self._entry_price = 0.0
self._bars_in_trade = 0
self._ema = ExponentialMovingAverage()
self._ema.Length = self._ema_len.Value
self._roc = RateOfChange()
self._roc.Length = self._perf_days.Value + 1
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ema, self._roc, self.OnProcess).Start()
self.StartProtection(
Unit(3, UnitTypes.Percent),
Unit(self._stop_pct.Value, UnitTypes.Percent)
)
def OnProcess(self, candle, ema_val, roc_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
opn = float(candle.OpenPrice)
ev = float(ema_val)
rv = float(roc_val)
body_pct = abs(close - opn) / self._prev_close * 100.0 if self._prev_close != 0 else 0.0
gt = float(self._gap_threshold.Value)
strong_move = body_pct >= gt
perf_pos = self._prev_roc > 0
if perf_pos and strong_move and self.Position == 0:
self.BuyMarket()
self._prev_close = close
self._prev_roc = rv
def CreateClone(self):
return pead_strategy()