Earnings Announcement Premium
Earnings Announcement Premium 策略在财报发布前几天买入股票,并在财报发布后不久退出。
细节
- 入场条件:在财报前
DaysBefore天买入。 - 方向:仅多头。
- 出场条件:在财报后
DaysAfter天卖出。 - 止损:无。
- 默认值:
DaysBefore = 5DaysAfter = 1CandleType = TimeSpan.FromMinutes(5).TimeFrame()
- 筛选:
- 类别:事件驱动
- 方向:多头
- 指标:日历
- 止损:无
- 复杂度:初级
- 时间框架:日线
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
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;
/// <summary>
/// Earnings announcement premium strategy that enters the primary instrument shortly before a synthetic earnings event and exits after the event passes.
/// </summary>
public class EarningsAnnouncementPremiumStrategy : Strategy
{
private readonly StrategyParam<int> _daysBefore;
private readonly StrategyParam<int> _daysAfter;
private readonly StrategyParam<int> _eventCycleBars;
private readonly StrategyParam<int> _trendLength;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _trend = null!;
private int _barsSinceEvent;
private int _cooldownRemaining;
private decimal _latestTrendValue;
/// <summary>
/// Number of bars before the synthetic earnings event to enter.
/// </summary>
public int DaysBefore
{
get => _daysBefore.Value;
set => _daysBefore.Value = value;
}
/// <summary>
/// Number of bars after the synthetic earnings event to exit.
/// </summary>
public int DaysAfter
{
get => _daysAfter.Value;
set => _daysAfter.Value = value;
}
/// <summary>
/// Distance between synthetic earnings events in finished bars.
/// </summary>
public int EventCycleBars
{
get => _eventCycleBars.Value;
set => _eventCycleBars.Value = value;
}
/// <summary>
/// Trend filter length.
/// </summary>
public int TrendLength
{
get => _trendLength.Value;
set => _trendLength.Value = value;
}
/// <summary>
/// Closed candles to wait before another position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Candle type to use for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="EarningsAnnouncementPremiumStrategy"/>.
/// </summary>
public EarningsAnnouncementPremiumStrategy()
{
_daysBefore = Param(nameof(DaysBefore), 3)
.SetRange(1, 10)
.SetDisplay("Days Before", "Bars before the synthetic earnings event to enter", "General");
_daysAfter = Param(nameof(DaysAfter), 1)
.SetRange(1, 10)
.SetDisplay("Days After", "Bars after the synthetic earnings event to exit", "General");
_eventCycleBars = Param(nameof(EventCycleBars), 18)
.SetRange(8, 80)
.SetDisplay("Event Cycle Bars", "Distance between synthetic earnings events in finished bars", "General");
_trendLength = Param(nameof(TrendLength), 12)
.SetRange(3, 50)
.SetDisplay("Trend Length", "Trend filter length", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 2)
.SetRange(0, 20)
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "Risk");
_stopLoss = Param(nameof(StopLoss), 2.5m)
.SetRange(0.5m, 10m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to process", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trend = null!;
_barsSinceEvent = 0;
_cooldownRemaining = 0;
_latestTrendValue = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Security == null)
throw new InvalidOperationException("Primary security is not specified.");
_trend = new SimpleMovingAverage { Length = TrendLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
StartProtection(
new Unit(2, UnitTypes.Percent),
new Unit(StopLoss, UnitTypes.Percent));
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_latestTrendValue = _trend.Process(candle).ToDecimal();
if (!_trend.IsFormed || !IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var barsToEvent = EventCycleBars - (_barsSinceEvent % EventCycleBars);
var bullishWindow = barsToEvent <= DaysBefore && barsToEvent > 0 && candle.ClosePrice >= _latestTrendValue * 0.995m;
var exitWindow = _barsSinceEvent % EventCycleBars == DaysAfter;
if (_cooldownRemaining == 0 && Position == 0 && bullishWindow)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (Position > 0 && exitWindow)
{
SellMarket(Position);
_cooldownRemaining = CooldownBars;
}
_barsSinceEvent++;
}
}
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 SimpleMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class earnings_announcement_premium_strategy(Strategy):
"""Earnings announcement premium strategy that enters the primary instrument
shortly before a synthetic earnings event and exits after the event passes."""
def __init__(self):
super(earnings_announcement_premium_strategy, self).__init__()
self._days_before = self.Param("DaysBefore", 3) \
.SetRange(1, 10) \
.SetDisplay("Days Before", "Bars before the synthetic earnings event to enter", "General")
self._days_after = self.Param("DaysAfter", 1) \
.SetRange(1, 10) \
.SetDisplay("Days After", "Bars after the synthetic earnings event to exit", "General")
self._event_cycle_bars = self.Param("EventCycleBars", 18) \
.SetRange(8, 80) \
.SetDisplay("Event Cycle Bars", "Distance between synthetic earnings events in finished bars", "General")
self._trend_length = self.Param("TrendLength", 12) \
.SetRange(3, 50) \
.SetDisplay("Trend Length", "Trend filter length", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 2) \
.SetRange(0, 20) \
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "Risk")
self._stop_loss = self.Param("StopLoss", 2.5) \
.SetRange(0.5, 10.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to process", "General")
self._trend = None
self._bars_since_event = 0
self._cooldown_remaining = 0
self._latest_trend_value = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
result = []
if self.Security is not None:
result.append((self.Security, self.candle_type))
return result
def OnReseted(self):
super(earnings_announcement_premium_strategy, self).OnReseted()
self._trend = None
self._bars_since_event = 0
self._cooldown_remaining = 0
self._latest_trend_value = 0.0
def OnStarted2(self, time):
super(earnings_announcement_premium_strategy, self).OnStarted2(time)
self._trend = SimpleMovingAverage()
self._trend.Length = int(self._trend_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(float(self._stop_loss.Value), UnitTypes.Percent)
)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
civ = CandleIndicatorValue(self._trend, candle)
civ.IsFinal = True
trend_result = self._trend.Process(civ)
self._latest_trend_value = float(trend_result)
if not self._trend.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
event_cycle = int(self._event_cycle_bars.Value)
days_before = int(self._days_before.Value)
days_after = int(self._days_after.Value)
cooldown = int(self._cooldown_bars.Value)
bars_to_event = event_cycle - (self._bars_since_event % event_cycle)
bullish_window = bars_to_event <= days_before and bars_to_event > 0 and float(candle.ClosePrice) >= self._latest_trend_value * 0.995
exit_window = (self._bars_since_event % event_cycle) == days_after
if self._cooldown_remaining == 0 and self.Position == 0 and bullish_window:
self.BuyMarket()
self._cooldown_remaining = cooldown
elif self.Position > 0 and exit_window:
self.SellMarket(self.Position)
self._cooldown_remaining = cooldown
self._bars_since_event += 1
def CreateClone(self):
return earnings_announcement_premium_strategy()