Стратегия по модели "Вечерняя звезда"
Паттерн "Вечерняя звезда" является зеркальным отражением "Утренней звезды" и указывает на возможное формирование вершины. Он начинается с сильной бычьей свечи, за которой следует небольшая свеча неопределенности, и заканчивается медвежьей свечой, закрывающейся ниже середины первой.
Тестирование показывает среднегодичную доходность около 100%. Стратегию лучше запускать на рынке Форекс.
Алгоритм следит за последовательностями из трех свечей. Когда формируется шаблон, открывается короткая позиция со стопом выше максимума маленькой средней свечи. Позиции закрываются, когда цена падает ниже минимума подтверждающей свечи или если сработает стоп.
Так как сетап подразумевает быстрое разворотное движение от перекупленных условий, сделки обычно нацелены на короткие импульсные движения вниз.
Детали
- Условия входа: Паттерн "Вечерняя звезда" из трех свечей.
- Лонг/Шорт: Только шорт.
- Условия выхода: Цена ниже минимума подтверждающей свечи или стоп‑лосс.
- Стопы: Да, выше максимума средней свечи.
- Значения по умолчанию:
CandleType= 5 минутStopLossPercent= 1
- Фильтры:
- Категория: Паттерн
- Направление: Шорт
- Индикаторы: Свечные модели
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Short-only three-candle Evening Star with middle-candle-high stop and confirmation-low target.
/// </summary>
public class EveningStarStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly List<ICandleMessage> _recent = new();
private decimal? _stopPrice;
private decimal? _targetPrice;
private Order _entryOrder;
private Order _exitOrder;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public EveningStarStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Evening Star pattern timeframe", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 1m).SetNotNegative()
.SetDisplay("Stop above middle high (%)", "Buffer above the middle candle's high; zero places the stop at that high", "Protection");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
ClearState();
}
private void ClearState()
{
_recent.Clear();
_stopPrice = _targetPrice = null;
_entryOrder = _exitOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearState();
var asks = new Subscription(DataType.Level1, Security);
asks.MarketData.BuildField = Level1Fields.BestAskPrice;
SubscribeLevel1(asks).Bind(ProcessAsk).Start();
var candles = SubscribeCandles(CandleType);
candles.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, candles);
DrawOwnTrades(area);
}
}
private static bool IsPending(Order order)
=> order is not null && order.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessAsk(Level1ChangeMessage message)
{
if (message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask && ask > 0m)
CheckExit(ask, ask);
}
private bool CheckExit(decimal high, decimal low)
{
if (Position >= 0m || IsPending(_exitOrder))
return false;
// With only a completed bar's range, prefer the adverse stop when both levels touched.
if (_stopPrice is decimal stop && high >= stop ||
_targetPrice is decimal target && low < target)
{
_exitOrder = BuyMarket(Math.Abs(Position));
_stopPrice = _targetPrice = null;
return true;
}
return false;
}
private void Append(ICandleMessage candle)
{
_recent.Add(candle);
if (_recent.Count > 2)
_recent.RemoveAt(0);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (Position < 0m && CheckExit(candle.HighPrice, candle.LowPrice))
{
Append(candle);
return;
}
if (IsPending(_entryOrder) || IsPending(_exitOrder))
{
Append(candle);
return;
}
if (Position == 0m && _recent.Count == 2 && IsFormedAndOnlineAndAllowTrading())
{
var first = _recent[0];
var middle = _recent[1];
var firstBody = first.ClosePrice - first.OpenPrice;
var middleBody = Math.Abs(middle.ClosePrice - middle.OpenPrice);
var thirdBody = candle.OpenPrice - candle.ClosePrice;
var firstMidpoint = (first.HighPrice + first.LowPrice) / 2m;
if (firstBody > 0m && middleBody < firstBody / 2m &&
thirdBody > 0m && candle.ClosePrice < firstMidpoint)
{
_stopPrice = middle.HighPrice * (1m + StopLossPercent / 100m);
_targetPrice = candle.LowPrice;
_entryOrder = SellMarket(Volume);
}
}
Append(candle);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, OrderStates, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class evening_star_strategy(Strategy):
"""Short-only Evening Star with a middle-candle-high stop and confirmation-low target."""
def __init__(self):
super(evening_star_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Evening Star pattern timeframe", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetNotNegative().SetDisplay("Stop above middle high (%)", "Buffer above the middle candle's high; zero places the stop at that high", "Protection")
self._clear_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _clear_state(self):
self._recent = []
self._stop_price = None
self._target_price = None
self._entry_order = None
self._exit_order = None
def OnReseted(self):
super(evening_star_strategy, self).OnReseted()
self._clear_state()
def OnStarted2(self, time):
super(evening_star_strategy, self).OnStarted2(time)
self._clear_state()
asks = Subscription(DataType.Level1, self.Security)
asks.MarketData.BuildField = Level1Fields.BestAskPrice
self.SubscribeLevel1(asks).Bind(self._process_ask).Start()
candles = self.SubscribeCandles(self.candle_type)
candles.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawOwnTrades(area)
@staticmethod
def _is_pending(order):
return order is not None and order.State not in (OrderStates.Done, OrderStates.Failed)
def _process_ask(self, message):
if message.Changes.ContainsKey(Level1Fields.BestAskPrice):
ask = message.Changes[Level1Fields.BestAskPrice]
if ask > 0:
self._check_exit(ask, ask)
def _check_exit(self, high, low):
if self.Position >= 0 or self._is_pending(self._exit_order):
return False
# With only a completed bar's range, prefer the adverse stop when both levels touched.
if ((self._stop_price is not None and high >= self._stop_price) or
(self._target_price is not None and low < self._target_price)):
self._exit_order = self.BuyMarket(Math.Abs(self.Position))
self._stop_price = None
self._target_price = None
return True
return False
def _append(self, candle):
self._recent.append(candle)
if len(self._recent) > 2:
self._recent.pop(0)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self.Position < 0 and self._check_exit(candle.HighPrice, candle.LowPrice):
self._append(candle)
return
if self._is_pending(self._entry_order) or self._is_pending(self._exit_order):
self._append(candle)
return
if self.Position == 0 and len(self._recent) == 2 and self.IsFormedAndOnlineAndAllowTrading():
first, middle = self._recent
first_body = first.ClosePrice - first.OpenPrice
middle_body = Math.Abs(middle.ClosePrice - middle.OpenPrice)
third_body = candle.OpenPrice - candle.ClosePrice
first_midpoint = (first.HighPrice + first.LowPrice) / Decimal(2)
if (first_body > 0 and middle_body < first_body / Decimal(2) and
third_body > 0 and candle.ClosePrice < first_midpoint):
self._stop_price = middle.HighPrice * (Decimal(1) + Decimal(self._stop_loss_percent.Value) / Decimal(100))
self._target_price = candle.LowPrice
self._entry_order = self.SellMarket(self.Volume)
self._append(candle)
def CreateClone(self):
return evening_star_strategy()