GitHub で見る
4026 – ピボット戦略
概要
この戦略は、MQL/8550 にある MetaTrader 4 ファイル (ピボット インジケーターおよび付随する Pivots_test エキスパート アドバイザ) を StockSharp の高レベルの Strategy API に移植します。毎日のフロアピボットレベルを計算し、中央ピボットで一対の反対の未決注文をステージングし、固定のストップロス、テイクプロフィット、およびトレーリングストップで結果の各ポジションを管理するという元の動作が維持されます。
ピボット計算
- この戦略は、構成可能な ピボット タイムフレーム (
PivotCandleType、デフォルトでは毎日) をサブスクライブします。
- その時間枠のローソク足が終了するたびに、前日の OHLC 価格から従来のフロアピボットレベルが導出されます。
Pivot = (High + Low + Close) / 3
R1 = 2 × Pivot − Low
S1 = 2 × Pivot − High
R2 = Pivot + (High − Low) と S2 = Pivot − (High − Low)
R3 = 2 × Pivot + High − 2 × Low と S3 = 2 × Pivot − (2 × High − Low)
- レベルは次のセッションの開始時にアクティブになります。これが発生すると、戦略は
AddInfoLog を通じて値をログに記録します (例: Pivot levels for 2024-04-05: P=1.0924, R1=1.0956, …)。
保留中の注文のワークフロー
ピボット レベルがアクティブになると、戦略はピボット価格で 2 つの未決注文が存在することを継続的に保証します。
- 買い指値 @
Pivot、S2 のポストフィル保護 SellStop (ストップロス) および R2 の SellLimit (テイクプロフィット)。
- 売りストップ @
Pivot、ポストフィル保護付き BuyStop(R2)および BuyLimit(S2)。
すべての注文は、高レベルのヘルパー メソッド BuyLimit、SellStop、SellLimit、および BuyStop を介して送信されます。注文が約定すると、コードはその方向の平均エントリー価格を再計算し、既存の保護注文をキャンセルし、オープンボリューム全体をカバーする新しいストップ/リミットのペアを送信します(各ポジションが同じ S2/R2 プロテクションを継承する MetaTrader の動作を反映しています)。保護ストップまたはテイクプロフィットが実行されると、関連するヘルパーは自動的にクリアされます。
この戦略では単一のネット ポジションを使用するため、反対の約定は互いに相殺されます (MetaTrader のチケットベースのヘッジとは異なります)。これは、元の専門家からの唯一の意図的な逸脱です。
トレーリングストップロジック
TrailingStopPoints は、インジケーターポイントでの距離を定義します (楽器 PriceStep で乗算)。
- ロングポジションの場合、価格が平均エントリーを上回ってその距離を超えて移動すると、トレーリングストップがアクティブになります。その後、保護用の
SellStop が市場の近くに移動されます。
- ショート ポジションの場合はミラー ロジックが適用され、価格が有利に推移するにつれて
BuyStop が下がります。
- 追跡更新は、
CandleType を通じて選択された日中シリーズ (デフォルトでは 15 分足のローソク足) によって駆動されます。
パラメーター
| パラメータ |
説明 |
デフォルト |
OrderVolume |
各未決注文のボリューム (ロット/契約)。 |
0.1 |
TrailingStopPoints |
トレーリングストップの距離 (ポイント単位)。 0 は後続ロジックを無効にします。 |
30 |
CandleType |
トレーリングとセッションスケジュールの維持に使用される日中のローソク足シリーズ。 |
15m の時間枠 |
PivotCandleType |
毎日のピボット レベルを計算するために使用される時間枠。 |
1D の時間枠 |
LogPivotUpdates |
true の場合、ピボット レベルは変更されるたびに戦略ログに書き込まれます。 |
true |
すべての数値パラメータは StrategyParam<T> を通じて公開されるため、StockSharp インフラストラクチャ内で最適化できます。
ロギングと診断
- ピボット更新は
AddInfoLog を通じてルーティングされ、MetaTrader Comment/ObjectSetText 出力を置き換えます。
- 保護注文管理、ポジション処理、トレーリング ロジックは、StockSharp の高レベル ヘルパーのみに依存します。低レベルの注文登録や指標バッファーは使用されません。
使用上の注意
- 選択した証券の日足と日中の両方のローソク足を提供するコネクターに戦略を接続します。
- 必要に応じて、機器のステップを調整します(
PriceStep は自動検出されます。フォールバックは 0.0001 です)。
- 必要に応じて、元の MT4 設定と一致するように
OrderVolume、TrailingStopPoints、またはローソク足のタイプを調整します。
要求されたとおり、このポートには Python バージョンが提供されていません。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that calculates classic floor pivot levels from daily candles and trades
/// breakouts around the central pivot. Goes long on close above pivot, short on close below.
/// Uses S2/R2 as stop/target levels.
/// </summary>
public class PivotsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private decimal _pivotLevel;
private decimal _r1, _r2, _s1, _s2;
private decimal? _previousClose;
private decimal? _entryPrice;
private bool _pivotReady;
private readonly List<decimal> _dailyHighs = new();
private readonly List<decimal> _dailyLows = new();
private readonly List<decimal> _dailyCloses = new();
private DateTime _currentDay;
private decimal _dayHigh;
private decimal _dayLow;
private decimal _dayClose;
public PivotsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signal generation", "General");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_pivotLevel = 0m;
_r1 = _r2 = _s1 = _s2 = 0m;
_previousClose = null;
_entryPrice = null;
_pivotReady = false;
_dailyHighs.Clear();
_dailyLows.Clear();
_dailyCloses.Clear();
_currentDay = DateTime.MinValue;
_dayHigh = 0m;
_dayLow = decimal.MaxValue;
_dayClose = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = 2 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
var candleDay = candle.OpenTime.Date;
// Track daily OHLC
if (candleDay != _currentDay)
{
if (_currentDay != DateTime.MinValue && _dayHigh > 0m)
{
// Previous day completed, calculate pivots
var high = _dayHigh;
var low = _dayLow;
var close = _dayClose;
_pivotLevel = (high + low + close) / 3m;
_r1 = 2m * _pivotLevel - low;
_s1 = 2m * _pivotLevel - high;
_r2 = _pivotLevel + (high - low);
_s2 = _pivotLevel - (high - low);
_pivotReady = true;
}
_currentDay = candleDay;
_dayHigh = candle.HighPrice;
_dayLow = candle.LowPrice;
_dayClose = candle.ClosePrice;
}
else
{
if (candle.HighPrice > _dayHigh) _dayHigh = candle.HighPrice;
if (candle.LowPrice < _dayLow) _dayLow = candle.LowPrice;
_dayClose = candle.ClosePrice;
}
if (!_pivotReady)
{
_previousClose = candle.ClosePrice;
return;
}
if (_previousClose is null)
{
_previousClose = candle.ClosePrice;
return;
}
// Manage open positions
if (Position > 0)
{
// Exit long at R2 (take profit) or S1 (stop loss)
if (candle.HighPrice >= _r2 || candle.LowPrice <= _s1)
{
SellMarket(Math.Abs(Position));
_entryPrice = null;
}
}
else if (Position < 0)
{
// Exit short at S2 (take profit) or R1 (stop loss)
if (candle.LowPrice <= _s2 || candle.HighPrice >= _r1)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
}
}
// Entry signals based on pivot cross
if (Position == 0)
{
var crossAbovePivot = _previousClose.Value <= _pivotLevel && candle.ClosePrice > _pivotLevel;
var crossBelowPivot = _previousClose.Value >= _pivotLevel && candle.ClosePrice < _pivotLevel;
if (crossAbovePivot)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
}
else if (crossBelowPivot)
{
SellMarket();
_entryPrice = candle.ClosePrice;
}
}
_previousClose = candle.ClosePrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, DateTime
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage
class pivots_strategy(Strategy):
def __init__(self):
super(pivots_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for signal generation", "General")
self._pivot_level = 0.0
self._r1 = 0.0
self._r2 = 0.0
self._s1 = 0.0
self._s2 = 0.0
self._previous_close = None
self._entry_price = None
self._pivot_ready = False
self._current_day = None
self._day_high = 0.0
self._day_low = float('inf')
self._day_close = 0.0
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(pivots_strategy, self).OnStarted2(time)
self._sma = SimpleMovingAverage()
self._sma.Length = 2
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._sma, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
candle_day = candle.OpenTime.Date
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._current_day is None or candle_day != self._current_day:
if self._current_day is not None and self._day_high > 0:
h = self._day_high
l = self._day_low
c = self._day_close
self._pivot_level = (h + l + c) / 3.0
self._r1 = 2.0 * self._pivot_level - l
self._s1 = 2.0 * self._pivot_level - h
self._r2 = self._pivot_level + (h - l)
self._s2 = self._pivot_level - (h - l)
self._pivot_ready = True
self._current_day = candle_day
self._day_high = high
self._day_low = low
self._day_close = close
else:
if high > self._day_high:
self._day_high = high
if low < self._day_low:
self._day_low = low
self._day_close = close
if not self._pivot_ready:
self._previous_close = close
return
if self._previous_close is None:
self._previous_close = close
return
if self.Position > 0:
if high >= self._r2 or low <= self._s1:
self.SellMarket(Math.Abs(self.Position))
self._entry_price = None
elif self.Position < 0:
if low <= self._s2 or high >= self._r1:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = None
if self.Position == 0:
cross_above_pivot = self._previous_close <= self._pivot_level and close > self._pivot_level
cross_below_pivot = self._previous_close >= self._pivot_level and close < self._pivot_level
if cross_above_pivot:
self.BuyMarket()
self._entry_price = close
elif cross_below_pivot:
self.SellMarket()
self._entry_price = close
self._previous_close = close
def OnReseted(self):
super(pivots_strategy, self).OnReseted()
self._pivot_level = 0.0
self._r1 = 0.0
self._r2 = 0.0
self._s1 = 0.0
self._s2 = 0.0
self._previous_close = None
self._entry_price = None
self._pivot_ready = False
self._current_day = None
self._day_high = 0.0
self._day_low = float('inf')
self._day_close = 0.0
def CreateClone(self):
return pivots_strategy()