Buy & Sell Bullish Engulfing 策略
当出现多头吞没形态并满足可选的趋势过滤条件时,该策略开多单。仓位规模按照当前资金的百分比计算,仓位通过止盈或止损自动平仓。
细节
- 入场条件:多头吞没形态,可选 SMA 趋势过滤。
- 方向:仅做多。
- 出场条件:止盈或止损。
- 止损:是,包含止盈和止损。
- 默认值:
CandleType= 15 分钟TakeProfitPercent= 2StopLossPercent= 2OrderPercent= 30TrendMode= SMA50
- 过滤器:
- 类别:形态
- 方向:多头
- 指标:K线形态,SMA
- 止损:是
- 复杂度:低
- 时间框架:日内
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
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;
public enum BullishEngulfingTrendMode
{
None,
SMA50,
}
/// <summary>
/// Buys bullish body engulfings, optionally following a below-SMA50 bearish bar.
/// Allocates a percentage of current equity and closes longs through local SL/TP.
/// </summary>
public class BuySellBullishEngulfingStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _orderPercent;
private readonly StrategyParam<BullishEngulfingTrendMode> _trendMode;
private (decimal Open, decimal Close, decimal Sma, bool SmaReady)? _previousBar;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public decimal OrderPercent { get => _orderPercent.Value; set => _orderPercent.Value = value; }
public BullishEngulfingTrendMode TrendMode { get => _trendMode.Value; set => _trendMode.Value = value; }
public BuySellBullishEngulfingStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame());
_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m).SetRange(0m, 100m);
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetRange(0m, 100m);
_orderPercent = Param(nameof(OrderPercent), 30m).SetRange(0m, 100m).SetGreaterThanZero();
_trendMode = Param(nameof(TrendMode), BullishEngulfingTrendMode.SMA50);
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
_previousBar = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (TrendMode is not (BullishEngulfingTrendMode.None or BullishEngulfingTrendMode.SMA50))
throw new ArgumentOutOfRangeException(nameof(TrendMode));
_previousBar = null;
StartProtection(new Unit(TakeProfitPercent, UnitTypes.Percent), new Unit(StopLossPercent, UnitTypes.Percent),
useMarketOrders: true, isLocalStop: true);
var sma = new SimpleMovingAverage { Length = TrendMode == BullishEngulfingTrendMode.SMA50 ? 50 : 1 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, (candle, value) => ProcessCandle(candle, value, sma.IsFormed)).Start();
// Feed real bid updates to the native long-position protection between candle closes.
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = Level1Fields.BestBidPrice;
SubscribeLevel1(quotes).Bind(_ => { }).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
if (TrendMode == BullishEngulfingTrendMode.SMA50)
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue, bool smaReady)
{
if (candle.State != CandleStates.Finished)
return;
var previous = _previousBar;
_previousBar = (candle.OpenPrice, candle.ClosePrice, smaValue, smaReady);
if (previous is not { } bar || Position != 0m || !IsFormedAndOnlineAndAllowTrading())
return;
var engulfing = bar.Open > bar.Close && candle.ClosePrice > candle.OpenPrice
&& candle.OpenPrice <= bar.Close && candle.ClosePrice >= bar.Open;
var trend = TrendMode == BullishEngulfingTrendMode.None || bar.SmaReady && bar.Close < bar.Sma;
if (!engulfing || !trend || candle.ClosePrice <= 0m)
return;
var equity = Portfolio.CurrentValue ?? Portfolio.BeginValue ?? 0m;
var step = Security.VolumeStep ?? 1m;
if (equity <= 0m || step <= 0m)
return;
var volume = equity * OrderPercent / 100m / candle.ClosePrice;
volume = Math.Floor(Math.Min(volume, Security.MaxVolume ?? decimal.MaxValue) / step) * step;
if (volume > 0m && volume >= (Security.MinVolume ?? step))
BuyMarket(volume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal, Convert, Math
from StockSharp.Messages import DataType, CandleStates, Level1Fields, Unit, UnitTypes
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class buy_sell_bullish_engulfing_strategy(Strategy):
"""Long-only body engulfing, optional prior-bar SMA50 trend, equity sizing and local SL/TP."""
def __init__(self):
super(buy_sell_bullish_engulfing_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._take_profit_percent = self.Param("TakeProfitPercent", 2.0).SetRange(0.0, 100.0)
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetRange(0.0, 100.0)
self._order_percent = self.Param("OrderPercent", 30.0).SetRange(0.0, 100.0).SetGreaterThanZero()
self._trend_mode = self.Param("TrendMode", "SMA50")
self._previous_bar = None
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value), (self.Security, DataType.Level1)]
def OnReseted(self):
super(buy_sell_bullish_engulfing_strategy, self).OnReseted()
self._previous_bar = None
def OnStarted2(self, time):
super(buy_sell_bullish_engulfing_strategy, self).OnStarted2(time)
if self._trend_mode.Value not in ("None", "SMA50"):
raise ValueError("TrendMode must be None or SMA50.")
self._previous_bar = None
self.StartProtection(Unit(float(self._take_profit_percent.Value), UnitTypes.Percent),
Unit(float(self._stop_loss_percent.Value), UnitTypes.Percent),
useMarketOrders=True, isLocalStop=True)
sma = SimpleMovingAverage()
sma.Length = 50 if self._trend_mode.Value == "SMA50" else 1
def process(candle, value):
self._process_candle(candle, value, sma.IsFormed)
subscription = self.SubscribeCandles(self._candle_type.Value)
subscription.Bind(sma, process).Start()
# Bid updates drive native long-position protection between finished candles.
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = Level1Fields.BestBidPrice
self.SubscribeLevel1(quotes).Bind(lambda message: None).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
if self._trend_mode.Value == "SMA50":
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_value, sma_ready):
if candle.State != CandleStates.Finished:
return
previous = self._previous_bar
self._previous_bar = (candle.OpenPrice, candle.ClosePrice, sma_value, sma_ready)
if previous is None or self.Position != 0 or not self.IsFormedAndOnlineAndAllowTrading():
return
engulfing = (previous[0] > previous[1] and candle.ClosePrice > candle.OpenPrice and
candle.OpenPrice <= previous[1] and candle.ClosePrice >= previous[0])
trend = (self._trend_mode.Value == "None" or
previous[3] and previous[1] < previous[2])
if not engulfing or not trend or candle.ClosePrice <= 0:
return
equity = self.Portfolio.CurrentValue
if equity is None:
equity = self.Portfolio.BeginValue
step = self.Security.VolumeStep
if step is None:
step = Decimal.One
if equity is None or equity <= 0 or step <= 0:
return
volume = equity * Convert.ToDecimal(self._order_percent.Value) / Decimal(100) / candle.ClosePrice
maximum = self.Security.MaxVolume
if maximum is not None:
volume = Math.Min(volume, maximum)
volume = Math.Floor(volume / step) * step
minimum = self.Security.MinVolume
if minimum is None:
minimum = step
if volume > 0 and volume >= minimum:
self.BuyMarket(volume)
def CreateClone(self):
return buy_sell_bullish_engulfing_strategy()