GitHub で見る
NTK_07 グリッド戦略
概要
NTK_07 戦略は、元々は MetaTrader 4 用に書かれた対称指値注文グリッドです。現在の価格付近に一対の逆指値注文を配置し、構成可能な間隔、ストップロス、テイクプロフィット、トレーリングルールを使用してマーチンゲールスタイルのポジションピラミッドを管理します。 StockSharp ポートは元の動作を維持しながら、すべての設定を厳密に型指定された戦略パラメータとして公開します。
この戦略により、次のことが継続的に保証されます。
- アクティブな注文がない場合、買いストップと売りストップが市場の周囲に駐車されます。
- ブレイクアウトが約定された後、ヘッジを防ぐために反対の未決注文がキャンセルされます。
- 同じ方向の追加注文は、
LotLimit を超えるまで、以前のサイズの Multiplier 倍で追加できます。
- それ以上のスケーリングが許可されない場合、アクティブなポジションはトレーリングストップと、オプションで動的に拡張されたテイクプロフィットによって保護されます。
- 保護的なストップ注文と利益確定注文は、出来高や目標価格が変化するたびに自動的に再作成されるため、オープンポジション全体が常に同じ決済レベルを共有します。
取引ロジック
- セッション フィルター。 土曜日と日曜日、または現在の時間が
[StartHour, EndHour] 以外の場合、取引はスキップされます。時間範囲は元の MT4 ロジックと一致します。EndHour = 24 により、終日取引が可能になります。
- 資本チェック。 ポートフォリオが添付されている場合、注文が作成される前に、現在の口座価値が少なくとも
MinCapital である必要があります。
- チャネル ブレイクアウト (オプション)。
ChannelPeriod がゼロより大きい場合、最後に完了した ChannelPeriod ローソク足の最高値と最低値が追跡されます。 UseChannelCenterに応じて:
false – 両方の未決注文は、売値が検出された範囲外にある場合(ブレイクアウト取引)にのみ送信されます。
true – 価格が範囲の中間点に戻ったときに注文が送信されます(平均回帰スタイル)。
- 最初の未決注文。 アクティブな注文がない場合、買いストップは最高売値よりも
NetStepPips 上に配置され、売りストップは最高入札よりも NetStepPips 下に配置されます。基本ボリュームは資金管理モジュールによって定義されます。
- ポジションのスケーリング 注文が約定された後、反対の未決注文はキャンセルされます。別の注文が同じ方向ですでにアクティブである場合、次の未決注文は
RoundVolume(previousVolume × Multiplier) を使用して NetStepPips 離れたところに配置されます。次のボリュームが計算された LotLimit を超えると、ストラテジーはグリッドへの追加を停止します。
- ストップロスとテイクプロフィット オープンポジションが変更されるたびに、戦略は保護ストップと(オプションで)集約されたロングエクスポージャーまたはショートエクスポージャーのテイクプロフィット注文を再作成します。距離は
StopLossPips と TakeProfitPips から導出されます。
- 損益分岐点ロジック。
UseBreakEven = true と価格が最後に約定した注文を超えて BreakEvenOffsetPips 移動すると、ストップロスは出来高加重平均エントリー価格に移動します (PriceRoundingFactor を使用して四捨五入されます)。
- トレーリング動作。 次のスケーリング ステップが許可されない場合、戦略は最高/最低のローソク足を使用して、ストップを市場に向けて
TrailingStopPips だけ移動させます。 TrailProfit = true の場合、利食い距離もシフトされるため、常に最後のローソク足の極値から TakeProfitPips 離れたままになります。 UseMovingAverageFilter = true と価格が移動平均に対して取引されている場合、トレーリング距離は半分に減り、移動平均付近の元の半ステップのトレーリング動作をエミュレートします。
資金管理
このポートは、ManagementMode パラメータを通じて 3 つのオリジナルの資金管理ルールをサポートしています。
| モード |
説明 |
Fixed |
新しい注文ごとに InitialLot を使用し、注文ごとのサイズの上限を LotLimit にします。 |
BalanceBased |
ポートフォリオ残高から開始ロットを再計算します: ceil(balance / 1000 × PercentRisk / 100)。結果は繰り返し Multiplier で除算され、最小のグリッド順序が投影され、LotRoundingFactor で四捨五入されます。元の LotLimit が理論上の最大ロットサイズになります。 |
Progressive |
InitialLot をベース ボリュームとして維持しますが、グリッド レベルごとに Multiplier を乗算して理論上の最大次数を予測します。 |
すべての注文は LotRoundingFactor (デフォルトは 10 => 0.1 増分) を使用して四捨五入され、損益分岐点価格は PriceRoundingFactor (デフォルトは 10000 => 0.0001 増分) で四捨五入されます。
パラメーター
| 名前 |
デフォルト |
説明 |
NetStepPips |
23 |
連続するグリッド レベル間の距離。 |
StopLossPips |
115 |
すべてのポジションに適用されるストップロス距離。無効にするには 0 に設定します。 |
TakeProfitPips |
300 |
集約されたポジションのテイクプロフィット距離。無効にするには 0 に設定します。 |
TrailingStopPips |
75 |
スケーリングが不可能になると、トレーリングストップ距離が有効になります。 |
Multiplier |
1.7 |
次のグリッド レベルのボリューム乗数。 |
TrailProfit |
true |
有効にすると、テイクプロフィットはトレーリングストップと一緒にシフトされます。 |
ManagementMode |
Progressive |
選択された資金管理ルール。 |
InitialLot |
1 |
基本注文量。 |
LotLimit |
7 |
単一の未決注文に許可される最大ロット サイズ。 |
MaxTrades |
4 |
グリッド レベルの最大数。 |
PercentRisk |
10 |
残高ベースの資金管理で使用される残高の割合。 |
MinCapital |
5000 |
取引前に必要なポートフォリオの最小値。 |
UseBreakEven |
false |
損益分岐点調整を有効にします。 |
BreakEvenOffsetPips |
5 |
損益分岐点に必要な利益のしきい値 (pips 単位)。 |
UseMovingAverageFilter |
false |
移動平均を考慮したトレーリング ロジックを有効にします。 |
MovingAverageLength |
100 |
フィルターで使用される移動平均の長さ。 |
MovingAverageShift |
0 |
移動平均に適用されるシフト (> 0 の場合、前のローソク足の値が使用されます)。 |
StartHour |
0 |
許可されている最も早い取引時間 (0 ~ 23)。 |
EndHour |
24 |
最新の許可された取引時間 (包括的)。 |
ChannelPeriod |
0 |
ブレークアウト/センター フィルターのルックバック ウィンドウ。フィルターを無効にするには 0 に設定します。 |
UseChannelCenter |
false |
ブレイクアウト (false) スタイル エントリとミッドポイント (true) スタイル エントリを切り替えます。 |
LotRoundingFactor |
10 |
ボリュームを四捨五入するときに使用される除算器。 |
PriceRoundingFactor |
10000 |
損益分岐点価格を四捨五入するときに使用される除算器。 |
CandleType |
15分の時間枠 |
範囲検出とトレーリング計算用の有効なキャンドル タイプ。 |
実装メモ
- オーダーブックは、保留中の注文を発注する前に正確な最高買値/売値を取得するために購読されます。ブックが利用できない場合、戦略はローソク足の終値に戻ります。
- 高レベルの API は、既存の注文を変更するのではなく、新しい注文を登録するためのより安全なヘルパーを公開しているため、保護的なストップとターゲットは変更されずに再作成されます。
- 利用可能な履歴を超えた移動平均シフト値は最新の値にフォールバックし、MetaTrader 実装に近い動作を維持しながら null 参照を防ぎます。
- すべての価格計算は
Security.ShrinkPrice を通じて正規化されるため、ストップレベルとリミットレベルは常に商品ティックサイズを考慮します。
使用のヒント
- ブローカーがポートフォリオのサイズに応じたスケーリングを必要とする場合は、
Strategy.Volume を設定して想定取引サイズの乗数を定義します。
- エキゾチックな目盛りサイズでシンボルをテストする場合は、丸め演算が意味のあるものになるように、それに応じて
LotRoundingFactor と PriceRoundingFactor を調整します。
- デフォルトのパラメータは、2008 年 1 月 1 日から 2008 年 11 月までの EURUSD H1 データの元の EA から取得されました。他の資産または時間枠については、再最適化することをお勧めします。
- グリッドは大きな指向性エクスポージャを蓄積する可能性があるため、リスクを制御するために常に
LotLimit と MaxTrades の値を監視してください。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Grid strategy that enters on ATR-based breakouts and scales positions
/// using a multiplier on subsequent breakouts in the same direction.
/// </summary>
public class Ntk07Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _gridMultiplier;
private decimal _referencePrice;
private decimal _entryPrice;
private bool _initialized;
public Ntk07Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "Period for ATR.", "Indicators");
_gridMultiplier = Param(nameof(GridMultiplier), 1.5m)
.SetDisplay("Grid Multiplier", "ATR multiplier for grid step.", "Grid");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal GridMultiplier
{
get => _gridMultiplier.Value;
set => _gridMultiplier.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_referencePrice = 0;
_entryPrice = 0;
_initialized = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_referencePrice = 0;
_entryPrice = 0;
_initialized = false;
var atr = new AverageTrueRange { Length = AtrLength };
var ema = new ExponentialMovingAverage { Length = 20 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (atrValue <= 0)
return;
var close = candle.ClosePrice;
var gridStep = atrValue * GridMultiplier;
if (!_initialized)
{
_referencePrice = close;
_initialized = true;
return;
}
// Position management
if (Position > 0)
{
// Take profit at 2x grid step from entry
if (_entryPrice > 0 && close >= _entryPrice + gridStep * 2)
{
SellMarket();
_referencePrice = close;
}
// Stop-loss at 1.5x grid step from entry
else if (_entryPrice > 0 && close <= _entryPrice - gridStep * 1.5m)
{
SellMarket();
_referencePrice = close;
}
}
else if (Position < 0)
{
if (_entryPrice > 0 && close <= _entryPrice - gridStep * 2)
{
BuyMarket();
_referencePrice = close;
}
else if (_entryPrice > 0 && close >= _entryPrice + gridStep * 1.5m)
{
BuyMarket();
_referencePrice = close;
}
}
// Entry: price moves a full grid step from reference
if (Position == 0)
{
if (close > _referencePrice + gridStep && close > emaValue)
{
_entryPrice = close;
_referencePrice = close;
BuyMarket();
}
else if (close < _referencePrice - gridStep && close < emaValue)
{
_entryPrice = close;
_referencePrice = close;
SellMarket();
}
}
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange, ExponentialMovingAverage
class ntk07_strategy(Strategy):
def __init__(self):
super(ntk07_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "Period for ATR", "Indicators")
self._grid_multiplier = self.Param("GridMultiplier", 1.5) \
.SetDisplay("Grid Multiplier", "ATR multiplier for grid step", "Grid")
self._reference_price = 0.0
self._entry_price = 0.0
self._initialized = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def GridMultiplier(self):
return self._grid_multiplier.Value
def OnStarted2(self, time):
super(ntk07_strategy, self).OnStarted2(time)
self._reference_price = 0.0
self._entry_price = 0.0
self._initialized = False
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
self._ema = ExponentialMovingAverage()
self._ema.Length = 20
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self._ema, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_value, ema_value):
if candle.State != CandleStates.Finished:
return
atr_val = float(atr_value)
ema_val = float(ema_value)
if atr_val <= 0:
return
close = float(candle.ClosePrice)
grid_step = atr_val * float(self.GridMultiplier)
if not self._initialized:
self._reference_price = close
self._initialized = True
return
# Position management
if self.Position > 0:
if self._entry_price > 0 and close >= self._entry_price + grid_step * 2:
self.SellMarket()
self._reference_price = close
elif self._entry_price > 0 and close <= self._entry_price - grid_step * 1.5:
self.SellMarket()
self._reference_price = close
elif self.Position < 0:
if self._entry_price > 0 and close <= self._entry_price - grid_step * 2:
self.BuyMarket()
self._reference_price = close
elif self._entry_price > 0 and close >= self._entry_price + grid_step * 1.5:
self.BuyMarket()
self._reference_price = close
# Entry: price moves a full grid step from reference
if self.Position == 0:
if close > self._reference_price + grid_step and close > ema_val:
self._entry_price = close
self._reference_price = close
self.BuyMarket()
elif close < self._reference_price - grid_step and close < ema_val:
self._entry_price = close
self._reference_price = close
self.SellMarket()
def OnReseted(self):
super(ntk07_strategy, self).OnReseted()
self._reference_price = 0.0
self._entry_price = 0.0
self._initialized = False
def CreateClone(self):
return ntk07_strategy()