PEAD-Strategie
Diese Strategie handelt den Post-Earnings Announcement Drift nach einer positiven EPS-Überraschung und einem Gap nach oben. Sie geht am Tag nach den Earnings long, wenn der Kurs mit einem Gap nach oben öffnet und die jüngste Entwicklung positiv ist, und verwendet einen EMA-Trailing, einen festen Stop/Breakeven und eine maximale Haltedauer.
Details
- Einstiegskriterien: Positive EPS-Überraschung, Gap nach oben nach den Earnings und positive Vorperformance.
- Long/Short: Nur Long.
- Ausstiegskriterien: Täglicher EMA-Crossunder, fester Stop/Breakeven oder maximale Haltebars.
- Stops: Fester Stop mit Breakeven.
- Standardwerte:
GapThreshold= 1EpsSurpriseThreshold= 5PerfDays= 20StopPct= 8EmaLen= 50MaxHoldBars= 50CandleType= TimeSpan.FromDays(1)
- Filter:
- Kategorie: Earnings
- Richtung: Long
- Indikatoren: EMA
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Täglich
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
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()