Ma SAR ADX Bind 戦略
概要
この戦略は、オリジナルのMaSarADX.mq5 MetaTrader 5エキスパートアドバイザーをStockSharpの高レベルAPIに変換したものです。システムは単純移動平均トレンドフィルターと方向性移動指数(ADX)シグナル、そしてParabolic SARトレーリングストップを組み合わせています。取引の決定は完成したローソク足でのみ評価され、MQLバージョンの「新しいバーの最初のティック」の動作を再現します。ローソク足の終値が移動平均トレンドとADX方向性バランスの両方に一致すると、ポジションが開かれます。Parabolic SARはSARドットの反対側に価格が交差したときに完全清算を強制することで、取引方向とエグジットの両方を導きます。
インジケーターとデータ
- 単純移動平均(SMA) – プライマリトレンド方向フィルターを提供します。デフォルトの長さ: 100ローソク足。
- 平均方向性指数(ADX) – 方向性強度を確認するための+DIと−DIを供給します。デフォルトの長さ: 14。
- Parabolic SAR – ストップアンドリバースオーバーレイとして機能し、エグジット条件を定義します。デフォルトの加速度: 0.02; 最大加速度: 0.10。
- ローソク足 – 任意のタイムフレームをリクエストできます。デフォルトで戦略は1時間のローソク足をサブスクライブしますが、シンボルとテストレジームに合わせてパラメーターを調整できます。
実装はStockSharpのローソク足ストリームをサブスクライブし、BindExヘルパーを使用してすべての3つのインジケーターをバインドし、各コールバックが同じローソク足の同期された値を受け取るようにします。
トレーディングロジック
ロングエントリー
- ローソク足の終値が移動平均を上回っています。
- +DIが−DI以上であり、強気の方向性圧力を示しています。
- ローソク足の終値がParabolic SARの値を上回っています。
- 現在ロングポジションがオープンしていません(
Position <= 0)。
すべてのルールが一致すると、設定された基本ボリュームにショートポジションをカバーするのに必要なサイズを加えた買い成行注文が送信されます。
ショートエントリー
- ローソク足の終値が移動平均を下回っています。
- +DIが−DI以下であり、弱気の方向性圧力を示しています。
- ローソク足の終値がParabolic SARの値を下回っています。
- 現在ショートポジションがオープンしていません(
Position >= 0)。
すべてのショートルールが一致すると、売り成行注文が出されます。
エグジット
- ロングポジションは価格がParabolic SARを下回るとすぐに閉じられます。
- ショートポジションは価格がParabolic SARを上回ると決済されます。
別のストップロスやテイクプロフィットレベルは追加されません。SAR交差がオリジナルのエキスパートアドバイザーに従う唯一のエグジットシグナルです。エグジットが新しいエントリーの前に評価されるため、戦略は同じローソク足でショートからロング(またはその逆)に転換しません。
パラメーター
| 名前 |
説明 |
デフォルト |
注意事項 |
MaPeriod |
トレンドフィルターを定義するために使用する単純移動平均の長さ。 |
100 |
最適化可能、ゼロより大きくなければなりません。 |
AdxPeriod |
+DIと−DIを生成するADX計算の期間。 |
14 |
最適化可能、ゼロより大きくなければなりません。 |
SarStep |
Parabolic SARの加速係数とインクリメント。 |
0.02 |
MQLのstepパラメーターと同等。 |
SarMax |
Parabolic SARの最大加速係数。 |
0.10 |
MQLのmaximum設定を反映。 |
Volume |
新規エントリーの基本注文サイズ。 |
1 |
MetaTraderバージョンのマージンベースのロットサイジングを置き換えます。実際の注文サイズはVolume + |Position|で、反転が既存のエクスポージャーを解消します。 |
CandleType |
StockSharpを通じてサブスクライブされるローソク足データタイプ。 |
1時間 |
任意のタイムフレームに調整可能。 |
実装メモ
- インジケーター処理はStockSharpの高レベル
BindExパイプラインを使用し、SMA、ADX、SARが手動バッファリングなしでロックステップで更新されることを保証します。
AllowTradingが一時的に無効になっていてもエグジットは実行され、リスクコントロールを常にアクティブに保ちます。
- グラフィングヘルパーが含まれています。プライマリパネルは価格、SMA、SARを表示し、セカンダリパネルは診断のためにADXインジケーターを表示します。
- ログステートメントは各取引決定を基礎インジケーター値とともに説明し、フォワードテストとデバッグを簡素化します。
使用ガイドライン
- DesignerまたはBacktesterで戦略を証券とポートフォリオに添付します。
- トレーディングホライズンに合わせてローソク足タイプを調整します(例: M15、H1、またはD1ローソク足)。
- 移動平均期間、ADX期間、SARパラメーターをインストゥルメントのボラティリティに合わせて調整します。
Volumeパラメーターを希望のポジションサイズに設定します。オリジナルスクリプトで使用されている適応的なマネーマネジメントが必要な場合は、注文を送信する前に独自のポートフォリオベースのサイジングを統合してください。
- 戦略を実行します。すべてのインジケーターが形成されるのに十分な履歴値を生成した後にのみ取引がトリガーされます。
オリジナルのエキスパートアドバイザーとの違い
- マージンベースのロット計算は固定の
Volumeパラメーターに置き換えられ、StockSharp内で戦略をブローカー中立に保ちます。
- 取引管理、インジケーター値、評価順(エントリー前のエグジット)はMetaTraderのリファレンスロジックに厳密に従います。
- ソースコード内のすべてのコメントはプロジェクトガイドラインに準拠して英語です。
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>
/// Conversion of the MaSarADX MetaTrader strategy to StockSharp high level API.
/// Combines a moving average, ADX directional movement and Parabolic SAR for entries and exits.
/// </summary>
public class MaSarAdxBindStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _sarStep;
private readonly StrategyParam<decimal> _sarMax;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousHigh;
private decimal? _previousLow;
private decimal? _previousClose;
private decimal _smoothedPlusDm;
private decimal _smoothedMinusDm;
private decimal _smoothedTrueRange;
private int _adxSamples;
/// <summary>
/// Moving average period used for the trend filter.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Average Directional Index calculation period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Acceleration step for the Parabolic SAR indicator.
/// </summary>
public decimal SarStep
{
get => _sarStep.Value;
set => _sarStep.Value = value;
}
/// <summary>
/// Maximum acceleration factor for the Parabolic SAR indicator.
/// </summary>
public decimal SarMax
{
get => _sarMax.Value;
set => _sarMax.Value = value;
}
/// <summary>
/// Type of candles processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="MaSarAdxBindStrategy"/>.
/// </summary>
public MaSarAdxBindStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 120)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Length of the trend moving average", "Indicators")
.SetOptimize(20, 200, 10);
_adxPeriod = Param(nameof(AdxPeriod), 18)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Length of the Average Directional Index", "Indicators")
.SetOptimize(7, 28, 1);
_sarStep = Param(nameof(SarStep), 0.02m)
.SetRange(0.005m, 0.2m)
.SetDisplay("SAR Step", "Acceleration step for Parabolic SAR", "Indicators")
;
_sarMax = Param(nameof(SarMax), 0.1m)
.SetRange(0.05m, 1m)
.SetDisplay("SAR Maximum", "Maximum acceleration for Parabolic SAR", "Indicators")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to request", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousHigh = null;
_previousLow = null;
_previousClose = null;
_smoothedPlusDm = 0m;
_smoothedMinusDm = 0m;
_smoothedTrueRange = 0m;
_adxSamples = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Instantiate indicators used in the original MetaTrader script.
var movingAverage = new SimpleMovingAverage
{
Length = MaPeriod
};
var parabolicSar = new ParabolicSar
{
Acceleration = SarStep,
AccelerationStep = SarStep,
AccelerationMax = SarMax
};
// Subscribe to candle data and bind indicator updates to a single handler.
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(movingAverage, parabolicSar, ProcessCandle)
.Start();
// Draw the trading context for visual debugging when charts are available.
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, movingAverage);
DrawIndicator(area, parabolicSar);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal movingAverage, decimal sar)
{
// Work only with completed candles to mirror the original first-tick logic.
if (candle.State != CandleStates.Finished)
return;
var (plusDi, minusDi, isReady) = UpdateDirectionalMovement(candle);
if (!isReady)
return;
// Always allow risk exits even if trading is temporarily disabled.
if (Position > 0 && candle.ClosePrice < sar)
{
SellMarket();
return;
}
if (Position < 0 && candle.ClosePrice > sar)
{
BuyMarket();
return;
}
// Entry conditions replicated from the MetaTrader version.
var bullishSignal = candle.ClosePrice > movingAverage && plusDi >= minusDi && candle.ClosePrice > sar;
var bearishSignal = candle.ClosePrice < movingAverage && plusDi <= minusDi && candle.ClosePrice < sar;
if (bullishSignal && Position <= 0)
{
BuyMarket();
return;
}
if (bearishSignal && Position >= 0)
{
SellMarket();
}
}
private (decimal plusDi, decimal minusDi, bool isReady) UpdateDirectionalMovement(ICandleMessage candle)
{
if (_previousHigh is not decimal previousHigh ||
_previousLow is not decimal previousLow ||
_previousClose is not decimal previousClose)
{
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_previousClose = candle.ClosePrice;
return (0m, 0m, false);
}
var upMove = candle.HighPrice - previousHigh;
var downMove = previousLow - candle.LowPrice;
var plusDm = upMove > downMove && upMove > 0m ? upMove : 0m;
var minusDm = downMove > upMove && downMove > 0m ? downMove : 0m;
var trueRange = Math.Max(
candle.HighPrice - candle.LowPrice,
Math.Max(
Math.Abs(candle.HighPrice - previousClose),
Math.Abs(candle.LowPrice - previousClose)));
if (_adxSamples < AdxPeriod)
{
_smoothedPlusDm += plusDm;
_smoothedMinusDm += minusDm;
_smoothedTrueRange += trueRange;
_adxSamples++;
}
else
{
_smoothedPlusDm = _smoothedPlusDm - (_smoothedPlusDm / AdxPeriod) + plusDm;
_smoothedMinusDm = _smoothedMinusDm - (_smoothedMinusDm / AdxPeriod) + minusDm;
_smoothedTrueRange = _smoothedTrueRange - (_smoothedTrueRange / AdxPeriod) + trueRange;
}
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_previousClose = candle.ClosePrice;
if (_adxSamples < AdxPeriod || _smoothedTrueRange <= 0m)
return (0m, 0m, false);
return (
100m * _smoothedPlusDm / _smoothedTrueRange,
100m * _smoothedMinusDm / _smoothedTrueRange,
true);
}
}
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
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage, ParabolicSar
from StockSharp.Algo.Strategies import Strategy
class ma_sar_adx_bind_strategy(Strategy):
def __init__(self):
super(ma_sar_adx_bind_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 120)
self._adx_period = self.Param("AdxPeriod", 18)
self._sar_step = self.Param("SarStep", 0.02)
self._sar_max = self.Param("SarMax", 0.1)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2)))
self._previous_high = None
self._previous_low = None
self._previous_close = None
self._smoothed_plus_dm = 0.0
self._smoothed_minus_dm = 0.0
self._smoothed_true_range = 0.0
self._adx_samples = 0
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
@property
def AdxPeriod(self):
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def SarStep(self):
return self._sar_step.Value
@SarStep.setter
def SarStep(self, value):
self._sar_step.Value = value
@property
def SarMax(self):
return self._sar_max.Value
@SarMax.setter
def SarMax(self, value):
self._sar_max.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(ma_sar_adx_bind_strategy, self).OnStarted2(time)
self._previous_high = None
self._previous_low = None
self._previous_close = None
self._smoothed_plus_dm = 0.0
self._smoothed_minus_dm = 0.0
self._smoothed_true_range = 0.0
self._adx_samples = 0
ma = SimpleMovingAverage()
ma.Length = self.MaPeriod
sar = ParabolicSar()
sar.Acceleration = float(self.SarStep)
sar.AccelerationStep = float(self.SarStep)
sar.AccelerationMax = float(self.SarMax)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ma, sar, self.ProcessCandle).Start()
def ProcessCandle(self, candle, moving_average, sar):
if candle.State != CandleStates.Finished:
return
ma_val = float(moving_average)
sar_val = float(sar)
plus_di, minus_di, is_ready = self._update_directional_movement(candle)
if not is_ready:
return
close = float(candle.ClosePrice)
# Exit conditions
if self.Position > 0 and close < sar_val:
self.SellMarket()
return
if self.Position < 0 and close > sar_val:
self.BuyMarket()
return
# Entry conditions
bullish_signal = close > ma_val and plus_di >= minus_di and close > sar_val
bearish_signal = close < ma_val and plus_di <= minus_di and close < sar_val
if bullish_signal and self.Position <= 0:
self.BuyMarket()
return
if bearish_signal and self.Position >= 0:
self.SellMarket()
def _update_directional_movement(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._previous_high is None or self._previous_low is None or self._previous_close is None:
self._previous_high = high
self._previous_low = low
self._previous_close = close
return (0.0, 0.0, False)
up_move = high - self._previous_high
down_move = self._previous_low - low
plus_dm = up_move if (up_move > down_move and up_move > 0.0) else 0.0
minus_dm = down_move if (down_move > up_move and down_move > 0.0) else 0.0
true_range = max(high - low, max(abs(high - self._previous_close), abs(low - self._previous_close)))
adx_period = int(self.AdxPeriod)
if self._adx_samples < adx_period:
self._smoothed_plus_dm += plus_dm
self._smoothed_minus_dm += minus_dm
self._smoothed_true_range += true_range
self._adx_samples += 1
else:
self._smoothed_plus_dm = self._smoothed_plus_dm - (self._smoothed_plus_dm / adx_period) + plus_dm
self._smoothed_minus_dm = self._smoothed_minus_dm - (self._smoothed_minus_dm / adx_period) + minus_dm
self._smoothed_true_range = self._smoothed_true_range - (self._smoothed_true_range / adx_period) + true_range
self._previous_high = high
self._previous_low = low
self._previous_close = close
if self._adx_samples < adx_period or self._smoothed_true_range <= 0.0:
return (0.0, 0.0, False)
return (
100.0 * self._smoothed_plus_dm / self._smoothed_true_range,
100.0 * self._smoothed_minus_dm / self._smoothed_true_range,
True)
def OnReseted(self):
super(ma_sar_adx_bind_strategy, self).OnReseted()
self._previous_high = None
self._previous_low = None
self._previous_close = None
self._smoothed_plus_dm = 0.0
self._smoothed_minus_dm = 0.0
self._smoothed_true_range = 0.0
self._adx_samples = 0
def CreateClone(self):
return ma_sar_adx_bind_strategy()