寄り付き後ロング ATRストップロス・テイクプロフィット戦略
この戦略は、市場の寄り付き時に抵抗線のブレイクアウト後、価格がボリンジャーバンドの中心線付近に留まっている場合にロングポジションを建てます。EMA、RSI、ADX、ATRフィルターを使用し、ATRベースのストップロスとテイクプロフィットで退出します。
詳細
- エントリー条件:
- ロング: 市場の寄り付き時に直近の抵抗線を上方ブレイク、価格がボリンジャーバンドの中心線付近、RSIが閾値を上回る、ADXが閾値を上回る、短期トレンドが上向きで押し目なし。
- ロング/ショート: ロングのみ。
- エグジット条件:
- ATRベースのストップロスまたはテイクプロフィット到達。
- ストップ:
- ATRベースのストップロスとテイクプロフィット。
- デフォルト値:
BB Length= 14BB Mult= 1.5EMA Length= 10EMA Long Length= 200RSI Length= 7RSI Threshold= 30ADX Length= 7ADX Threshold= 10ATR Length= 14ATR SL Mult= 2.0ATR TP Mult= 4.0SessionStart= 09:30 UTCSessionDuration= TimeSpan.FromHours(1)ResistancePeriod= 5BasisProximity= 1CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- フィルター:
- カテゴリ: トレンドフォロー
- 方向: Long
- インジケーター: Bollinger Bands, EMA, RSI, ADX, ATR
- ストップ: ATR
- 複雑さ: 中級
- 時間軸: 任意
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
計算と執行
全インジケーターが形成済みの確定足だけを使用する。抵抗は現在足を除く直前 ResistancePeriod 本の高値の最大値で、エントリーは Close > resistance を必要とする。開始窓は足の開始時刻のUTC時刻を用い、開始を含み終了を含まず、日付をまたぐ窓にも対応する。SessionStart は対象銘柄の開始時刻に設定する。
Bollinger中心は現在足を含む終値SMA、偏差は母標準偏差。近接条件は abs(Close - basis) <= (UpperBand - basis) * BasisProximity。短期EMAが長期EMAを上回り、Closeが短期EMAを上回ることが必要。押し戻しなしはシグナル足の Close >= Open と定義する。RSIとADXは閾値を厳密に上回る必要がある。
保有なし、未完了注文なしの場合だけ Volume を買う。エントリー足のWilder ATRでSL/TP距離を固定する。標準のローカル保護は実際の約定価格の下/上に水準を設定し(部分約定では出来高加重価格)、Level1最良買い気配と足の価格を監視して残りのロングを全決済する。開始窓の外でも決済は有効。後のATR変化で水準は動かない。ショート、EMA交差による決済、待機時間はない。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Long-only opening-session resistance breakouts with Bollinger, EMA, RSI and ADX filters
/// and actual-fill protection at frozen signal ATR distances.
/// </summary>
public class PostOpenLongAtrStopLossTakeProfitStrategy : Strategy
{
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMult;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _emaLongLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiThreshold;
private readonly StrategyParam<int> _adxLength;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrStopLossMultiplier;
private readonly StrategyParam<decimal> _atrTakeProfitMultiplier;
private readonly StrategyParam<TimeSpan> _sessionStart;
private readonly StrategyParam<TimeSpan> _sessionDuration;
private readonly StrategyParam<int> _resistancePeriod;
private readonly StrategyParam<decimal> _basisProximity;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _highs = new();
private Order _pendingOrder;
private Unit _stopDistance;
private Unit _takeDistance;
private bool _protectionStarted;
/// <summary>
/// Initializes the published indicator and ATR settings and a configurable daily UTC opening window.
/// </summary>
public PostOpenLongAtrStopLossTakeProfitStrategy()
{
_bbLength = Param(nameof(BbLength), 14).SetGreaterThanZero()
.SetDisplay("BB Length", "Bollinger close-price SMA and deviation period", "Indicators");
_bbMult = Param(nameof(BbMult), 1.5m).SetGreaterThanZero()
.SetDisplay("BB Mult", "Bollinger population deviation multiplier", "Indicators");
_emaLength = Param(nameof(EmaLength), 10).SetGreaterThanZero()
.SetDisplay("EMA Length", "Short close-price EMA period", "Indicators");
_emaLongLength = Param(nameof(EmaLongLength), 200).SetGreaterThanZero()
.SetDisplay("EMA Long Length", "Long close-price EMA period", "Indicators");
_rsiLength = Param(nameof(RsiLength), 7).SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_rsiThreshold = Param(nameof(RsiThreshold), 30m).SetRange(0m, 100m)
.SetDisplay("RSI Threshold", "RSI must be strictly above this value", "Indicators");
_adxLength = Param(nameof(AdxLength), 7).SetGreaterThanZero()
.SetDisplay("ADX Length", "ADX period", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 10m).SetRange(0m, 100m)
.SetDisplay("ADX Threshold", "ADX must be strictly above this value", "Indicators");
_atrLength = Param(nameof(AtrLength), 14).SetGreaterThanZero()
.SetDisplay("ATR Length", "Wilder ATR period", "Indicators");
_atrStopLossMultiplier = Param(nameof(AtrStopLossMultiplier), 2m).SetGreaterThanZero()
.SetDisplay("ATR SL Mult", "Frozen entry ATR stop distance", "Protection");
_atrTakeProfitMultiplier = Param(nameof(AtrTakeProfitMultiplier), 4m).SetGreaterThanZero()
.SetDisplay("ATR TP Mult", "Frozen entry ATR take-profit distance", "Protection");
_sessionStart = Param(nameof(SessionStart), TimeSpan.FromMinutes(570))
.SetRange(TimeSpan.Zero, TimeSpan.FromDays(1) - TimeSpan.FromTicks(1))
.SetDisplay("Session Start", "Inclusive daily opening time in UTC", "Entry");
_sessionDuration = Param(nameof(SessionDuration), TimeSpan.FromHours(1))
.SetRange(TimeSpan.FromTicks(1), TimeSpan.FromDays(1))
.SetDisplay("Session Duration", "Opening-window duration; the end is exclusive", "Entry");
_resistancePeriod = Param(nameof(ResistancePeriod), 5).SetGreaterThanZero()
.SetDisplay("Resistance Period", "Prior finished highs excluding the signal candle", "Entry");
_basisProximity = Param(nameof(BasisProximity), 1m).SetGreaterThanZero()
.SetDisplay("Basis Proximity", "Maximum distance from the basis in Bollinger half-widths", "Entry");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of finished signal candles", "General");
OrderRegistering += order => _pendingOrder = order;
}
/// <summary>Close-price Bollinger period.</summary>
public int BbLength { get => _bbLength.Value; set => _bbLength.Value = value; }
/// <summary>Bollinger deviation multiplier.</summary>
public decimal BbMult { get => _bbMult.Value; set => _bbMult.Value = value; }
/// <summary>Short close-price EMA period.</summary>
public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
/// <summary>Long close-price EMA period.</summary>
public int EmaLongLength { get => _emaLongLength.Value; set => _emaLongLength.Value = value; }
/// <summary>RSI period.</summary>
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
/// <summary>Exclusive lower RSI threshold.</summary>
public decimal RsiThreshold { get => _rsiThreshold.Value; set => _rsiThreshold.Value = value; }
/// <summary>ADX period.</summary>
public int AdxLength { get => _adxLength.Value; set => _adxLength.Value = value; }
/// <summary>Exclusive lower ADX threshold.</summary>
public decimal AdxThreshold { get => _adxThreshold.Value; set => _adxThreshold.Value = value; }
/// <summary>Wilder ATR period.</summary>
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <summary>Positive stop distance in signal ATR units.</summary>
public decimal AtrStopLossMultiplier { get => _atrStopLossMultiplier.Value; set => _atrStopLossMultiplier.Value = value; }
/// <summary>Positive take-profit distance in signal ATR units.</summary>
public decimal AtrTakeProfitMultiplier { get => _atrTakeProfitMultiplier.Value; set => _atrTakeProfitMultiplier.Value = value; }
/// <summary>Inclusive daily opening time in UTC.</summary>
public TimeSpan SessionStart { get => _sessionStart.Value; set => _sessionStart.Value = value; }
/// <summary>Opening-window duration; supports midnight wrap and excludes its end.</summary>
public TimeSpan SessionDuration { get => _sessionDuration.Value; set => _sessionDuration.Value = value; }
/// <summary>Number of prior finished highs defining resistance.</summary>
public int ResistancePeriod { get => _resistancePeriod.Value; set => _resistancePeriod.Value = value; }
/// <summary>Maximum basis distance in Bollinger half-widths.</summary>
public decimal BasisProximity { get => _basisProximity.Value; set => _basisProximity.Value = value; }
/// <summary>Candle type; five minutes by default.</summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_pendingOrder = null;
_stopDistance = _takeDistance = null;
_protectionStarted = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (SessionStart < TimeSpan.Zero || SessionStart >= TimeSpan.FromDays(1)
|| SessionDuration <= TimeSpan.Zero || SessionDuration > TimeSpan.FromDays(1))
throw new ArgumentException("The UTC opening time must be within a day and duration within (0, 24 hours].");
_highs.Clear();
_pendingOrder = null;
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = Level1Fields.BestBidPrice;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
var bb = new BollingerBands { Length = BbLength, Width = BbMult };
var fast = new ExponentialMovingAverage { Length = EmaLength };
var slow = new ExponentialMovingAverage { Length = EmaLongLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var adx = new AverageDirectionalIndex { Length = AdxLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx([bb, fast, slow, rsi, adx, atr], ProcessCandle, allowEmpty: true).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bb);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native bid-price protection runs before this callback.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished)
return;
var resistance = _highs.Count == ResistancePeriod ? _highs.Max() : (decimal?)null;
_highs.Enqueue(candle.HighPrice);
if (_highs.Count > ResistancePeriod)
_highs.Dequeue();
if (resistance is null || values.Any(value => value.IsEmpty || !value.IsFormed)
|| !IsFormedAndOnlineAndAllowTrading() || Position != 0m
|| _pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
var bb = (IBollingerBandsValue)values[0];
var adx = (IAverageDirectionalIndexValue)values[4];
if (bb.MovingAverage is not decimal basis || bb.UpBand is not decimal upper || adx.MovingAverage is not decimal strength)
return;
var age = (candle.OpenTime.TimeOfDay - SessionStart + TimeSpan.FromDays(1)).Ticks % TimeSpan.TicksPerDay;
var close = candle.ClosePrice;
var atr = values[5].ToDecimal();
if (age >= SessionDuration.Ticks || close <= resistance || close < candle.OpenPrice
|| Math.Abs(close - basis) > (upper - basis) * BasisProximity
|| close <= values[1].ToDecimal() || values[1].ToDecimal() <= values[2].ToDecimal()
|| values[3].ToDecimal() <= RsiThreshold || strength <= AdxThreshold || atr <= 0m)
return;
_stopDistance ??= new Unit();
_takeDistance ??= new Unit();
_stopDistance.Value = atr * AtrStopLossMultiplier;
_takeDistance.Value = atr * AtrTakeProfitMultiplier;
if (!_protectionStarted)
{
StartProtection(_takeDistance, _stopDistance, useMarketOrders: true, isLocalStop: true);
_protectionStarted = true;
}
RegisterOrder(new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = Sides.Buy,
Volume = Volume,
Comment = "Post-open breakout entry",
});
}
}
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 Array, ArgumentException, Decimal, TimeSpan
from StockSharp.Messages import DataType, CandleStates, Level1Fields, OrderStates, OrderTypes, Sides, Unit
from StockSharp.BusinessEntities import Order, Subscription
from StockSharp.Algo.Indicators import IIndicator, BollingerBands, ExponentialMovingAverage, RelativeStrengthIndex, AverageDirectionalIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class post_open_long_atr_stop_loss_take_profit_strategy(Strategy):
def __init__(self):
super(post_open_long_atr_stop_loss_take_profit_strategy, self).__init__()
self._bb_length = self.Param("BbLength", 14).SetGreaterThanZero() \
.SetDisplay("BB Length", "Bollinger close-price SMA and deviation period", "Indicators")
self._bb_mult = self.Param("BbMult", Decimal.Parse("1.5")).SetGreaterThanZero() \
.SetDisplay("BB Mult", "Bollinger population deviation multiplier", "Indicators")
self._ema_length = self.Param("EmaLength", 10).SetGreaterThanZero() \
.SetDisplay("EMA Length", "Short close-price EMA period", "Indicators")
self._ema_long_length = self.Param("EmaLongLength", 200).SetGreaterThanZero() \
.SetDisplay("EMA Long Length", "Long close-price EMA period", "Indicators")
self._rsi_length = self.Param("RsiLength", 7).SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI period", "Indicators")
self._rsi_threshold = self.Param("RsiThreshold", Decimal.Parse("30")).SetRange(Decimal.Zero, Decimal(100)) \
.SetDisplay("RSI Threshold", "RSI must be strictly above this value", "Indicators")
self._adx_length = self.Param("AdxLength", 7).SetGreaterThanZero() \
.SetDisplay("ADX Length", "ADX period", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", Decimal.Parse("10")).SetRange(Decimal.Zero, Decimal(100)) \
.SetDisplay("ADX Threshold", "ADX must be strictly above this value", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero() \
.SetDisplay("ATR Length", "Wilder ATR period", "Indicators")
self._atr_stop_loss_multiplier = self.Param("AtrStopLossMultiplier", Decimal.Parse("2")).SetGreaterThanZero() \
.SetDisplay("ATR SL Mult", "Frozen entry ATR stop distance", "Protection")
self._atr_take_profit_multiplier = self.Param("AtrTakeProfitMultiplier", Decimal.Parse("4")).SetGreaterThanZero() \
.SetDisplay("ATR TP Mult", "Frozen entry ATR take-profit distance", "Protection")
self._session_start = self.Param("SessionStart", TimeSpan.FromMinutes(570)) \
.SetRange(TimeSpan.Zero, TimeSpan.FromDays(1) - TimeSpan.FromTicks(1)) \
.SetDisplay("Session Start", "Inclusive daily opening time in UTC", "Entry")
self._session_duration = self.Param("SessionDuration", TimeSpan.FromHours(1)) \
.SetRange(TimeSpan.FromTicks(1), TimeSpan.FromDays(1)) \
.SetDisplay("Session Duration", "Opening-window duration; the end is exclusive", "Entry")
self._resistance_period = self.Param("ResistancePeriod", 5).SetGreaterThanZero() \
.SetDisplay("Resistance Period", "Prior finished highs excluding the signal candle", "Entry")
self._basis_proximity = self.Param("BasisProximity", Decimal.Parse("1")).SetGreaterThanZero() \
.SetDisplay("Basis Proximity", "Maximum distance from the basis in Bollinger half-widths", "Entry")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of finished signal candles", "General")
self._highs = []
self._pending_order = None
self._stop_distance = None
self._take_distance = None
self._protection_started = False
self.OrderRegistering += self._observe_order
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def _observe_order(self, order):
self._pending_order = order
def OnReseted(self):
super(post_open_long_atr_stop_loss_take_profit_strategy, self).OnReseted()
self._highs = []
self._pending_order = None
self._stop_distance = None
self._take_distance = None
self._protection_started = False
def OnStarted2(self, time):
super(post_open_long_atr_stop_loss_take_profit_strategy, self).OnStarted2(time)
start = self._session_start.Value
duration = self._session_duration.Value
if start < TimeSpan.Zero or start >= TimeSpan.FromDays(1) or duration <= TimeSpan.Zero or duration > TimeSpan.FromDays(1):
raise ArgumentException("The UTC opening time must be within a day and duration within (0, 24 hours].")
self._highs = []
self._pending_order = None
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = Level1Fields.BestBidPrice
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
bb = BollingerBands()
bb.Length = self._bb_length.Value
bb.Width = self._bb_mult.Value
fast = ExponentialMovingAverage()
fast.Length = self._ema_length.Value
slow = ExponentialMovingAverage()
slow.Length = self._ema_long_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
adx = AverageDirectionalIndex()
adx.Length = self._adx_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(Array[IIndicator]([bb, fast, slow, rsi, adx, atr]), self.OnProcess, True).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bb)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native bid-price protection runs before this callback.
pass
def OnProcess(self, candle, values):
if candle.State != CandleStates.Finished:
return
period = self._resistance_period.Value
resistance = max(self._highs) if len(self._highs) == period else None
self._highs.append(candle.HighPrice)
if len(self._highs) > period:
self._highs.pop(0)
pending = self._pending_order
if resistance is None or any(value.IsEmpty or not value.IsFormed for value in values) \
or not self.IsFormedAndOnlineAndAllowTrading() or self.Position != 0 \
or pending is not None and pending.State != OrderStates.Done and pending.State != OrderStates.Failed:
return
basis = values[0].MovingAverage
upper = values[0].UpBand
strength = values[4].MovingAverage
if basis is None or upper is None or strength is None:
return
age = (candle.OpenTime.TimeOfDay - self._session_start.Value + TimeSpan.FromDays(1)).Ticks % TimeSpan.TicksPerDay
close = candle.ClosePrice
fast = values[1].GetValue[Decimal](None)
slow = values[2].GetValue[Decimal](None)
rsi = values[3].GetValue[Decimal](None)
atr = values[5].GetValue[Decimal](None)
if age >= self._session_duration.Value.Ticks or close <= resistance or close < candle.OpenPrice \
or abs(close - basis) > (upper - basis) * self._basis_proximity.Value \
or close <= fast or fast <= slow or rsi <= self._rsi_threshold.Value \
or strength <= self._adx_threshold.Value or atr <= 0:
return
if self._stop_distance is None:
self._stop_distance = Unit()
self._take_distance = Unit()
self._stop_distance.Value = atr * self._atr_stop_loss_multiplier.Value
self._take_distance.Value = atr * self._atr_take_profit_multiplier.Value
if not self._protection_started:
self.StartProtection(self._take_distance, self._stop_distance, useMarketOrders=True, isLocalStop=True)
self._protection_started = True
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = Sides.Buy
order.Volume = self.Volume
order.Comment = "Post-open breakout entry"
self.RegisterOrder(order)
def CreateClone(self):
return post_open_long_atr_stop_loss_take_profit_strategy()