Previous Day High Low Long
ロング専用です。UTCセッション内で終値が前UTC日の観測highまたはlowを上抜け、標準ADXが上昇したとき入ります。実際の約定からの利益目標、観測価格トレーリング、セッション終了で決済します。
水準とエントリー
確定足のみ、既定は5分足。UTCの足開始日でhigh/lowを集計し、次の日付開始時に前日の観測最高high・最低lowを固定します。前日の最初の観測足は午前0時開始で、日付が連続している必要があります。最初の部分日や欠落日に水準を仮定しません。現在日の足は前日水準を拡張しません。
highクロスはpreviousClose <= previousDayHigh && close > previousDayHigh。lowも前日lowを上方向に横断します。標準AdxLength=11のADXが形成済みで、前の形成済み値より厳密に大きいことが必要です。追加のADXしきい値やDI方向フィルターはありません。
ポジションなしからVolumeの成行買いで、注文UTC時刻が[SessionStart,SessionEnd)内、既定09:30–16:00です。毎確定足で終値・日・ADXの履歴を更新し、拒否したクロスを後に持ち越しません。決済後の新しいクロスで再エントリーでき、日次回数制限や6時間待機はありません。ショートを開きません。
保護と終了
MaxProfit=150、MaxStopLoss=15は絶対価格距離で、百分率や銘柄の価格刻みではありません。実際の買い約定でentryPriceとhighWaterを開始し、追加の部分買いは出来高加重平均価格を更新します。減少時は基準を保持し、全決済でリセットします。
正の執行可能bidまたは確定closeでhighWaterを以前と現在価格の最大値に更新します。価格≤highWater-MaxStopLossまたは≥entryPrice+MaxProfitなら実際のロング全量を成行売りします。優先順位はセッション終了、トレーリング、利益目標です。足のhighを架空の執行可能気配に使いません。
1分の市場時刻タイマー、bid、確定足がSessionEnd以降または次のUTC日付に保有を閉じます。時刻は同じUTC日内で順序を持ち、00:00–24:00は翌午前0時終了です。未完了注文は重複を防ぎ、日・ADX履歴は更新を続けます。決済した足コールバックは再エントリーせず戻ります。
ローカル監視で、取引所ネイティブ逆指値は送信しません。足の間の気配による保護にはLevel1 bidが必要で、なければcloseを検査します。約定価格は発動しきい値と異なる場合があります。
| パラメーター | 既定値 |
|---|---|
MaxProfit / MaxStopLoss |
150 / 15、正の絶対価格距離 |
AdxLength |
11、正 |
SessionStart / SessionEnd |
09:30 / 16:00 UTC、開始を含み終了を含まない |
CandleType |
5分 |
C#/Pythonは既存BTC/TON履歴から日を集計し、Wilder ATR/DI/DX/ADXを独立計算します。各実際のエントリー・決済、前日両水準、ADX拒否、開始時の部分履歴、実約定、気配ストップ・目標、強制セッション終了を検証します。収益性は主張しません。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Buys a previous UTC day's high/low upward close cross with rising ADX during a UTC session.
/// Actual-fill profit targets, observed-price trailing stops and session-end closes protect longs.
/// </summary>
public class PreviousDayHighLowLongStrategy : Strategy
{
private readonly StrategyParam<decimal> _maxProfit;
private readonly StrategyParam<decimal> _maxStopLoss;
private readonly StrategyParam<int> _adxLength;
private readonly StrategyParam<TimeSpan> _sessionStart;
private readonly StrategyParam<TimeSpan> _sessionEnd;
private readonly StrategyParam<DataType> _candleType;
private DateTime? _day;
private decimal _dayHigh;
private decimal _dayLow;
private bool _isDayStartObserved;
private bool _isPreviousDayAvailable;
private decimal _previousDayHigh;
private decimal _previousDayLow;
private decimal? _previousClose;
private decimal? _previousAdx;
private DateTime? _entryDate;
private decimal _filledPosition;
private decimal _entryPrice;
private decimal _extreme;
private Order _pendingOrder;
/// <summary>Initializes profit150, trailing distance15 in price units, ADX11 and UTC09:30–16:00.</summary>
public PreviousDayHighLowLongStrategy()
{
_maxProfit = Param(nameof(MaxProfit), 150m).SetGreaterThanZero()
.SetDisplay("Maximum Profit", "Target distance above actual entry price, in absolute price units", "Protection");
_maxStopLoss = Param(nameof(MaxStopLoss), 15m).SetGreaterThanZero()
.SetDisplay("Maximum Stop Loss", "Trailing distance below highest observed price, in absolute price units", "Protection");
_adxLength = Param(nameof(AdxLength), 11).SetGreaterThanZero()
.SetDisplay("ADX Length", "Native directional-strength period; current ADX must rise", "Indicators");
_sessionStart = Param(nameof(SessionStart), TimeSpan.FromMinutes(570))
.SetDisplay("Session Start", "Inclusive UTC entry time", "Session");
_sessionEnd = Param(nameof(SessionEnd), TimeSpan.FromHours(16))
.SetDisplay("Session End", "Exclusive UTC entry time and mandatory close", "Session");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Finished candles supplying UTC day levels and ADX", "General");
OrderRegistering += order => _pendingOrder = order;
OwnTradeReceived += ObserveFill;
}
/// <summary>Positive profit target distance from actual entry price, in absolute price units.</summary>
public decimal MaxProfit { get => _maxProfit.Value; set => _maxProfit.Value = value; }
/// <summary>Positive trailing distance from highest observed price, in absolute price units.</summary>
public decimal MaxStopLoss { get => _maxStopLoss.Value; set => _maxStopLoss.Value = value; }
/// <summary>Positive native ADX period.</summary>
public int AdxLength { get => _adxLength.Value; set => _adxLength.Value = value; }
/// <summary>Inclusive UTC entry time, before SessionEnd.</summary>
public TimeSpan SessionStart { get => _sessionStart.Value; set => _sessionStart.Value = value; }
/// <summary>Exclusive UTC entry time and mandatory close, at most twenty-four hours.</summary>
public TimeSpan SessionEnd { get => _sessionEnd.Value; set => _sessionEnd.Value = value; }
/// <summary>Finished 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)];
private void ResetState()
{
_day = null;
_previousClose = _previousAdx = null;
_entryDate = null;
_dayHigh = _dayLow = _previousDayHigh = _previousDayLow = 0m;
_isDayStartObserved = _isPreviousDayAvailable = false;
_filledPosition = _entryPrice = _extreme = 0m;
_pendingOrder = null;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (MaxProfit <= 0m || MaxStopLoss <= 0m || AdxLength <= 0 || SessionStart < TimeSpan.Zero
|| SessionStart >= SessionEnd || SessionEnd > TimeSpan.FromDays(1))
throw new ArgumentException("Risk distances and ADX period must be positive; session times must be ordered within a UTC day.");
ResetState();
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = Level1Fields.BestBidPrice;
SubscribeLevel1(quotes).Bind(ObserveQuote).Start();
StartTimer(TimeSpan.FromMinutes(1), () => TrySessionExit());
var adx = new AverageDirectionalIndex { Length = AdxLength };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx([adx], ProcessCandle, allowEmpty: true).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private bool IsPending => _pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);
private void Submit(Sides side, decimal volume, string comment)
=> RegisterOrder(new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = volume,
Comment = comment,
});
private bool TrySessionExit()
{
var isOver = _entryDate is DateTime date && (CurrentTime.Date > date || CurrentTime.TimeOfDay >= SessionEnd);
if (!isOver || Position <= 0m || IsPending || !IsFormedAndOnlineAndAllowTrading())
return false;
Submit(Sides.Sell, Position, "Previous-day session close");
return true;
}
private void ObserveFill(Subscription subscription, MyTrade trade)
{
var previous = _filledPosition;
var volume = trade.Trade.Volume;
_filledPosition += trade.Order.Side == Sides.Buy ? volume : -volume;
if (trade.Order.Side == Sides.Buy && _filledPosition > 0m)
{
_entryPrice = (_entryPrice * previous + trade.Trade.Price * volume) / _filledPosition;
_extreme = previous == 0m ? trade.Trade.Price : Math.Max(_extreme, trade.Trade.Price);
}
if (_filledPosition == 0m)
_entryPrice = _extreme = 0m;
}
private void ObservePrice(decimal price)
{
if (TrySessionExit() || Position <= 0m || _entryPrice <= 0m || IsPending || !IsFormedAndOnlineAndAllowTrading())
return;
_extreme = Math.Max(_extreme, price);
var comment = price <= _extreme - MaxStopLoss ? "Previous-day trailing stop"
: price >= _entryPrice + MaxProfit ? "Previous-day profit target" : null;
if (comment != null)
Submit(Sides.Sell, Position, comment);
}
private void ObserveQuote(Level1ChangeMessage quote)
{
if (quote.TryGetDecimal(Level1Fields.BestBidPrice) is decimal price && price > 0m)
ObservePrice(price);
}
private void ObserveDay(ICandleMessage candle)
{
var date = candle.OpenTime.Date;
if (_day != date)
{
_isPreviousDayAvailable = _isDayStartObserved && _day == date.AddDays(-1);
_previousDayHigh = _dayHigh;
_previousDayLow = _dayLow;
_day = date;
_dayHigh = candle.HighPrice;
_dayLow = candle.LowPrice;
_isDayStartObserved = candle.OpenTime.TimeOfDay == TimeSpan.Zero;
}
else
{
_dayHigh = Math.Max(_dayHigh, candle.HighPrice);
_dayLow = Math.Min(_dayLow, candle.LowPrice);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished)
return;
var previousClose = _previousClose;
_previousClose = candle.ClosePrice;
ObserveDay(candle);
var previousAdx = _previousAdx;
var value = (IAverageDirectionalIndexValue)values[0];
var currentAdx = value.IsFormed ? value.MovingAverage : null;
if (currentAdx is not null)
_previousAdx = currentAdx;
if (Position > 0m)
{
ObservePrice(candle.ClosePrice);
return;
}
if (Position != 0m || IsPending || !IsFormedAndOnlineAndAllowTrading() || !_isPreviousDayAvailable
|| previousClose is not decimal close || previousAdx is not decimal prior || currentAdx is not decimal strength || strength <= prior
|| CurrentTime.TimeOfDay < SessionStart || CurrentTime.TimeOfDay >= SessionEnd)
return;
var isHighCross = close <= _previousDayHigh && candle.ClosePrice > _previousDayHigh;
var isLowCross = close <= _previousDayLow && candle.ClosePrice > _previousDayLow;
if (!isHighCross && !isLowCross)
return;
_entryDate = CurrentTime.Date;
Submit(Sides.Buy, Volume, "Previous-day ADX breakout");
}
}
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 CandleStates, DataType, ITickTradeMessage, Level1Fields, OrderStates, OrderTypes, Sides
from StockSharp.BusinessEntities import Order, Subscription
from StockSharp.Algo.Indicators import AverageDirectionalIndex, IIndicator
from StockSharp.Algo.Strategies import Strategy
class previous_day_high_low_long_strategy(Strategy):
def __init__(self):
super(previous_day_high_low_long_strategy, self).__init__()
self._max_profit = self.Param("MaxProfit", Decimal(150)).SetGreaterThanZero() \
.SetDisplay("Maximum Profit", "Target distance above actual entry price, in absolute price units", "Protection")
self._max_stop_loss = self.Param("MaxStopLoss", Decimal(15)).SetGreaterThanZero() \
.SetDisplay("Maximum Stop Loss", "Trailing distance below highest observed price, in absolute price units", "Protection")
self._adx_length = self.Param("AdxLength", 11).SetGreaterThanZero() \
.SetDisplay("ADX Length", "Native directional-strength period; current ADX must rise", "Indicators")
self._session_start = self.Param("SessionStart", TimeSpan.FromMinutes(570)) \
.SetDisplay("Session Start", "Inclusive UTC entry time", "Session")
self._session_end = self.Param("SessionEnd", TimeSpan.FromHours(16)) \
.SetDisplay("Session End", "Exclusive UTC entry time and mandatory close", "Session")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Finished candles supplying UTC day levels and ADX", "General")
tick_type = clr.GetClrType(ITickTradeMessage)
self._tick_price = tick_type.GetProperty("Price")
self._tick_volume = tick_type.GetProperty("Volume")
self._reset_state()
self.OrderRegistering += self._observe_order
self.OwnTradeReceived += self._observe_fill
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def _reset_state(self):
self._day = self._previous_close = self._previous_adx = self._entry_date = None
self._day_high = self._day_low = self._previous_day_high = self._previous_day_low = Decimal.Zero
self._is_day_start_observed = self._is_previous_day_available = False
self._filled_position = self._entry_price = self._extreme = Decimal.Zero
self._pending_order = None
def OnReseted(self):
super(previous_day_high_low_long_strategy, self).OnReseted()
self._reset_state()
def _observe_order(self, order):
self._pending_order = order
def OnStarted2(self, time):
super(previous_day_high_low_long_strategy, self).OnStarted2(time)
if self._max_profit.Value <= 0 or self._max_stop_loss.Value <= 0 or self._adx_length.Value <= 0 \
or self._session_start.Value < TimeSpan.Zero or self._session_start.Value >= self._session_end.Value \
or self._session_end.Value > TimeSpan.FromDays(1):
raise ArgumentException("Risk distances and ADX period must be positive; session times must be ordered within a UTC day.")
self._reset_state()
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = Level1Fields.BestBidPrice
self.SubscribeLevel1(quotes).Bind(self._observe_quote).Start()
self.StartTimer(TimeSpan.FromMinutes(1), self._try_session_exit)
adx = AverageDirectionalIndex()
adx.Length = self._adx_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(Array[IIndicator]([adx]), self.OnProcess, True).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _is_pending(self):
order = self._pending_order
return order is not None and order.State != OrderStates.Done and order.State != OrderStates.Failed
def _submit(self, side, volume, comment):
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = volume
order.Comment = comment
self.RegisterOrder(order)
def _try_session_exit(self):
date = self._entry_date
is_over = date is not None and (self.CurrentTime.Date > date or self.CurrentTime.TimeOfDay >= self._session_end.Value)
if not is_over or self.Position <= 0 or self._is_pending() or not self.IsFormedAndOnlineAndAllowTrading():
return False
self._submit(Sides.Sell, self.Position, "Previous-day session close")
return True
def _observe_fill(self, subscription, trade):
previous = self._filled_position
price = self._tick_price.GetValue(trade.Trade)
volume = self._tick_volume.GetValue(trade.Trade)
self._filled_position += volume if trade.Order.Side == Sides.Buy else -volume
if trade.Order.Side == Sides.Buy and self._filled_position > 0:
self._entry_price = (self._entry_price * previous + price * volume) / self._filled_position
self._extreme = price if previous == 0 else max(self._extreme, price)
if self._filled_position == 0:
self._entry_price = self._extreme = Decimal.Zero
def _observe_price(self, price):
if self._try_session_exit() or self.Position <= 0 or self._entry_price <= 0 \
or self._is_pending() or not self.IsFormedAndOnlineAndAllowTrading():
return
self._extreme = max(self._extreme, price)
comment = "Previous-day trailing stop" if price <= self._extreme - self._max_stop_loss.Value \
else "Previous-day profit target" if price >= self._entry_price + self._max_profit.Value else None
if comment is not None:
self._submit(Sides.Sell, self.Position, comment)
def _observe_quote(self, quote):
if quote.Changes.ContainsKey(Level1Fields.BestBidPrice) and quote.Changes[Level1Fields.BestBidPrice] > 0:
self._observe_price(quote.Changes[Level1Fields.BestBidPrice])
def _observe_day(self, candle):
date = candle.OpenTime.Date
if self._day != date:
self._is_previous_day_available = self._is_day_start_observed and self._day == date.AddDays(-1)
self._previous_day_high = self._day_high
self._previous_day_low = self._day_low
self._day = date
self._day_high = candle.HighPrice
self._day_low = candle.LowPrice
self._is_day_start_observed = candle.OpenTime.TimeOfDay == TimeSpan.Zero
else:
self._day_high = max(self._day_high, candle.HighPrice)
self._day_low = min(self._day_low, candle.LowPrice)
def OnProcess(self, candle, values):
if candle.State != CandleStates.Finished:
return
previous_close = self._previous_close
self._previous_close = candle.ClosePrice
self._observe_day(candle)
previous_adx = self._previous_adx
current_adx = values[0].MovingAverage if values[0].IsFormed else None
if current_adx is not None:
self._previous_adx = current_adx
if self.Position > 0:
self._observe_price(candle.ClosePrice)
return
if self.Position != 0 or self._is_pending() or not self.IsFormedAndOnlineAndAllowTrading() \
or not self._is_previous_day_available or previous_close is None or previous_adx is None \
or current_adx is None or current_adx <= previous_adx \
or self.CurrentTime.TimeOfDay < self._session_start.Value or self.CurrentTime.TimeOfDay >= self._session_end.Value:
return
high_cross = previous_close <= self._previous_day_high and candle.ClosePrice > self._previous_day_high
low_cross = previous_close <= self._previous_day_low and candle.ClosePrice > self._previous_day_low
if not high_cross and not low_cross:
return
self._entry_date = self.CurrentTime.Date
self._submit(Sides.Buy, self.Volume, "Previous-day ADX breakout")
def CreateClone(self):
return previous_day_high_low_long_strategy()