GitHub で見る
Sprut 保留注文グリッド戦略
概要
Sprut 保留注文グリッド戦略は、MetaTrader 5 エキスパートアドバイザー Sprut (barabashkakvn 版) を StockSharp の高水準ストラテジーフレームワーク内で再現したものです。現在の市場価格を中心に設定可能な買いと売りの保留注文グリッドを構築し、StockSharp のヘルパーメソッド(BuyStop、SellStop、BuyLimit、SellLimit)を使用して各注文の有効期間、ボリュームスケーリング、約定後の保護を管理します。
変換版はオリジナルエキスパートアドバイザーの思想を維持しています:
- 有効な各方向の最初の注文を手動価格、または最良気配値からのピップ単位の自動オフセットで配置する;
- ストップ注文とリミット注文に独立した間隔を使用してグリッドを段階的に拡張する;
- MT5 実装を反映した乗数を使用して注文ボリュームをスケーリングする;
- 約定した各注文にエントリー価格からのピップオフセットで表現された独自のストップロスとテイクプロフィットを設定する;
- 到達時に即座にポジションを清算し残りの保留注文を削除するグローバル利益・損失チェックポイントを適用する;
- オプションで指定された分数後に保留注文を失効させる。
ストラテジーの仕組み
- 市場データ — ストラテジーは最良 bid/ask を追跡するために板情報の更新を購読し、定期的なメンテナンスを実行するためにローソク足(デフォルト:1 分)を購読します。インジケーターは不要です。
- グリッド初期化 — 未決済ポジションも有効なグリッド注文もない場合、ストラテジーは4つの注文タイプそれぞれの初期価格を計算します:
- Buy Stop:最良 Ask +
DeltaFirstBuyStop(FirstBuyStop がゼロでない場合を除く)。
- Buy Limit:最良 Bid −
DeltaFirstBuyLimit(FirstBuyLimit がゼロでない場合を除く)。
- Sell Stop:最良 Bid −
DeltaFirstSellStop(FirstSellStop がゼロでない場合を除く)。
- Sell Limit:最良 Ask +
DeltaFirstSellLimit(FirstSellLimit がゼロでない場合を除く)。
各オフセットは銘柄の PriceStep を使用してピップから変換されます(フォールバック:0.0001)。
- 注文の積み上げ — 有効な各方向に対してストラテジーは
StepStop または StepLimit(ピップ単位)で区切られた CountOrders エントリーを作成します。ボリュームはオリジナルの計算式に従います:注文 #0 は基本ボリュームを使用し、注文 #N は係数が 1 より大きい場合に baseVolume * N * coefficient を使用します。ボリュームは Security.VolumeStep、Security.MinVolume、Security.MaxVolume を考慮して調整されます。
- 失効 —
ExpirationMinutes が正の値の場合、ストラテジーは各保留注文にタイムスタンプを付け、期限後に自動的にキャンセルします。
- 約定後の保護 — StockSharp がエントリー注文の完了を報告すると、ストラテジーは対応するストップロスとテイクプロフィット注文(
StopLoss と TakeProfit、ピップ単位)を登録します。距離がゼロの場合は該当する保護が無効になります。
- 利益チェックポイント — 新しいデータが到着するたびに実現済み PnL と未実現 PnL が再計算されます。
ProfitClose が正で到達した場合、または LossClose(通常は負)が下回った場合、ストラテジーは完全清算を要求します:ポジションの成行決済、すべてのグリッド注文のキャンセル、残存する保護注文のキャンセル。すべてがフラットになった後、自動的に取引が再開されます。
- 継続的なメンテナンス — 各更新で完了した注文を整理し、失効したアイテムを削除し、条件が許す場合に新しいグリッドの配置を試み、清算進行中は再セットを回避します。
パラメーター
| 名前 |
説明 |
デフォルト |
CountOrders |
有効な各方向の注文数。 |
5 |
FirstBuyStop, FirstBuyLimit, FirstSellStop, FirstSellLimit |
各方向の最初の注文の絶対価格(0 = 自動オフセットを使用)。 |
0 |
DeltaFirstBuyStop, DeltaFirstBuyLimit, DeltaFirstSellStop, DeltaFirstSellLimit |
自動価格決定時に最良 bid/ask に適用されるピップオフセット。 |
15 |
UseBuyStop, UseBuyLimit, UseSellStop, UseSellLimit |
各グリッド方向を有効または無効にする。 |
false |
StepStop, StepLimit |
連続するストップまたはリミット注文間の距離(ピップ)。 |
50 |
VolumeStop, VolumeLimit |
最初のストップ/リミット注文の基本ボリューム。 |
0.01 |
CoefficientStop, CoefficientLimit |
追加注文に適用される乗数(>1 で MT5 のスケーリング動作を維持)。 |
1.6 |
ProfitClose |
清算をトリガーする合計 PnL 閾値(通貨単位)。 |
10 |
LossClose |
清算をトリガーする合計 PnL の下限(通貨単位、通常は負)。 |
-100 |
ExpirationMinutes |
保留注文の有効期間(分、0 = キャンセルまで有効)。 |
60 |
StopLoss, TakeProfit |
約定後に作成される保護ストップ/テイク注文のピップ距離(0 で無効)。 |
50 / 0 |
CandleType |
定期的なメンテナンスに使用するローソク足シリーズ。 |
1 分ローソク足 |
使用上の注意
- 4 つのブールスイッチ(
UseBuyStop、UseBuyLimit、UseSellStop、UseSellLimit)のうち少なくとも 1 つを有効にしてグリッドが作成されるようにしてください。
- ピップ変換は銘柄の
PriceStep に依存します。特殊なティックサイズを持つ銘柄では同等の動作のためにオフセットの調整が必要な場合があります。
ProfitClose/LossClose は実現済み PnL(Strategy.PnL)と最新の最良 bid/ask から計算された未実現 PnL の合計を比較します。取引する銘柄の価格ステップメタデータが入力されていることを確認してください。
- 保護ストップとテイク注文は独立した StockSharp 注文です。ストラテジー外でポジションを手動決済した場合、ネットポジションがゼロに戻ると残存する保護注文はキャンセルされます。
CandleType パラメーターはメンテナンスの実行頻度のみを制御します。注文の配置は引き続き板情報の更新に即座に反応します。
MT5 エキスパートアドバイザーとの違い
- ポジション管理はネット方式:StockSharp は MT5 のネッティング方式と同様に銘柄ごとに単一のネットポジションを保持します。
- 保留注文の MT5 内蔵ストップロス/テイクプロフィットフィールドの代わりに、StockSharp の保護注文はエントリー注文が実行された後にのみ作成されます。
- ボリューム正規化は
Security.VolumeStep、MinVolume、MaxVolume を使用します。CFD や暗号通貨取引所での取引時はこれらの値を確認してください。
- ストラテジーは別個の 全決済 ボタンを公開しません — 清算ルーティンは PnL 閾値を通じて完全に自動化されており、
ProfitClose/LossClose が完全シャットダウンをトリガーしたオリジナルのエキスパートロジックに一致します。
使い始める
- 選択した
CandleType の板情報データとローソク足を少なくとも提供するコネクターにストラテジーを割り当てます。
- リスクプロファイルに合わせて 4 つの方向スイッチとボリュームパラメーターを設定します。
- 保護注文が必要な場合はストップロス/テイクプロフィットの距離を定義します(無効にするにはゼロに設定)。
- アカウント通貨と一致する値に
ProfitClose/LossClose を調整します。
- ストラテジーを開始します。最初の板情報スナップショットを受け取るまで待ってからグリッドを構築します。
Python 版 — 提供されていません。要求に従い C# 実装のみが含まれています。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Sprut Pending Order Grid strategy. Uses Highest/Lowest midline crossover (period 14).
/// </summary>
public class SprutPendingOrderGridStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _period;
private decimal? _prevMid;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int Period { get => _period.Value; set => _period.Value = value; }
public SprutPendingOrderGridStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_period = Param(nameof(Period), 14).SetGreaterThanZero().SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMid = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMid = null;
var highest = new Highest { Length = Period };
var lowest = new Lowest { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(highest, lowest, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
{
if (candle.State != CandleStates.Finished) return;
var mid = (high + low) / 2m;
var close = candle.ClosePrice;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevMid = mid; return; }
if (_prevMid == null) { _prevMid = mid; return; }
if (close > mid && close > _prevMid.Value && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (close < mid && close < _prevMid.Value && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
_prevMid = mid;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class sprut_pending_order_grid_strategy(Strategy):
def __init__(self):
super(sprut_pending_order_grid_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._period = self.Param("Period", 14) \
.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators")
self._prev_mid = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def Period(self):
return self._period.Value
def OnReseted(self):
super(sprut_pending_order_grid_strategy, self).OnReseted()
self._prev_mid = None
def OnStarted2(self, time):
super(sprut_pending_order_grid_strategy, self).OnStarted2(time)
self._prev_mid = None
highest = Highest()
highest.Length = self.Period
lowest = Lowest()
lowest.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle, high_value, low_value):
if candle.State != CandleStates.Finished:
return
hv = float(high_value)
lv = float(low_value)
mid = (hv + lv) / 2.0
close = float(candle.ClosePrice)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_mid = mid
return
if self._prev_mid is None:
self._prev_mid = mid
return
if close > mid and close > self._prev_mid and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close < mid and close < self._prev_mid and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_mid = mid
def CreateClone(self):
return sprut_pending_order_grid_strategy()