GitHub で見る
MA MACD ポジションアベレージング戦略
この戦略は、MetaTraderエキスパートアドバイザー**「MA MACD Position averaging」**の忠実な変換です。加重移動平均フィルターと
MACD比率チェックを組み合わせ、設定可能な数のpips分だけ価格が不利な方向に動くたびにポジションサイズを増やすマーチンゲール式
アベレージングモジュールを追加します。すべてのリスクパラメーターはpips単位で設定され、StockSharpが提供する銘柄メタデータを
使用して内部的に価格オフセットに変換されます。
取引ロジック
- インジケーター準備
- 設定可能な移動平均(
MaPeriod、MaMethod、MaAppliedPrice)が完成した足でサンプリングされます。SignalBarとMaShift
パラメーターは、MetaTraderが指定した数の足だけ遡り、水平オフセットで移動平均をプロットする機能をエミュレートします。
- MACDインジケーター(
MacdFastPeriod、MacdSlowPeriod、MacdSignalPeriod、MacdAppliedPrice)が同じ足で処理されます。
戦略は、インジケーターAPIを直接呼び出すことなく履歴値にアクセスできるよう、MACDのメインラインとシグナルラインを小さな
ローリングバッファに保存します。
- エントリー条件
- ロング:両MACD線がゼロ以下で、比率
MACDmain / MACDsignalがMacdRatio以上で、足のクローズがサンプリングされた
移動平均より上にあり、価格と平均の距離が少なくともIndentPipspipsある。
- ショート:両MACD線がゼロ以上で、比率が
MacdRatioより上で、足のクローズが移動平均より下にあり、それらの距離が
少なくともIndentPipspipsある。
- 新規エントリーは戦略にポジションがないときのみ許可されます。アベレージングサイクルがすでに進行中の場合、シグナルロジックは
スキップされ、アベレージングルールのみが適用されます。
- アベレージングモジュール
- ロングポジションが存在し、価格が最良(最低)のロングエントリーから少なくとも
StepLossingPips下落した場合、戦略は
追加のロング取引を開き、そのボリュームは最後のレッグボリュームにLotCoefficientを掛けた値(銘柄ボリュームステップで
丸め)に等しい。
- ショートポジションが存在し、価格が最良(最高)のショートエントリーから少なくとも
StepLossingPips上昇した場合、同じ
LotCoefficient乗数を使用して新しいショートレッグが追加されます。
- 両方向でエクスポージャーが検出された場合(通常の条件下では起こらないはず)、戦略は一貫性を回復するためにすべてのレッグを
即座に閉じます。
- 保護決済
- 各レッグは価格単位(
StopLossPips、TakeProfitPips)で表現された個別のストップロスとテイクプロフィットレベルを保存します。
完成した各足で、戦略は足の値幅が保存されたレベルのいずれかをクロスしたかどうかを確認し、そうであればレッグを成行注文で
閉じます。
- トレーリングストップ(
TrailingStopPips、TrailingStepPips)はオプションです。価格がレッグに有利な方向に
TrailingStopPips + TrailingStepPips動いたら、ストップは現在のクローズからTrailingStopPipspips後ろに移動します。
ストップは価格が少なくともTrailingStepPipspipsの追加進歩をした場合にのみ締め付けられます。
- 保守管理
- ボリューム命令は銘柄のボリュームステップに合わせられ、許容最小値/最大値にクリップされます。戦略は二重処理を避けるために
完全に形成された足(
CandleStates.Finished)でのみ実行されます。
パラメーター
| パラメーター |
型 |
デフォルト |
説明 |
CandleType |
DataType |
TimeSpan.FromHours(1).TimeFrame() |
インジケーター計算に使用する時間軸。 |
OrderVolume |
decimal |
0.1 |
最初のエントリーのベースロットサイズ。 |
StopLossPips |
int |
50 |
pips単位のストップロス距離(0でストップ無効)。 |
TakeProfitPips |
int |
50 |
pips単位のテイクプロフィット距離(0でターゲット無効)。 |
TrailingStopPips |
int |
5 |
pips単位のトレーリングストップオフセット。トレーリングを有効にするには正の値が必要。 |
TrailingStepPips |
int |
5 |
トレーリングストップが再び動く前に必要な追加pip距離。 |
StepLossingPips |
int |
30 |
新しいアベレージングレッグをトリガーするpips単位の価格後退。 |
LotCoefficient |
decimal |
2.0 |
アベレージング時に前回レッグボリュームに適用される乗数。 |
SignalBar |
int |
0 |
インジケーターサンプリング時に遡る完成足数。 |
MaPeriod |
int |
15 |
足単位の移動平均の長さ。 |
MaShift |
int |
0 |
移動平均値に適用される水平シフト(足単位)。 |
MaMethod |
MovingAverageMethod |
Weighted |
移動平均の平滑化アルゴリズム(単純、指数、平滑、加重)。 |
MaAppliedPrice |
AppliedPriceType |
Weighted |
移動平均の入力として使用される足の価格。 |
IndentPips |
int |
4 |
エントリー前に価格と移動平均の間に必要な最小pip差。 |
MacdFastPeriod |
int |
12 |
MACDフィルターの高速EMAの長さ。 |
MacdSlowPeriod |
int |
26 |
MACDフィルターの低速EMAの長さ。 |
MacdSignalPeriod |
int |
9 |
MACDフィルターのシグナルラインの長さ。 |
MacdAppliedPrice |
AppliedPriceType |
Weighted |
MACD計算に使用される適用価格。 |
MacdRatio |
decimal |
0.9 |
取引を許可するために必要な最小MACDメイン/シグナル比率。 |
pip変換
すべてのpipベースの設定(StopLossPips、TakeProfitPips、TrailingStopPips、TrailingStepPips、StepLossingPips、
IndentPips)は銘柄のPriceStepで乗算されます。銘柄が小数点以下3または5桁の場合、小数点以下の分数建値のMetaTraderの
「pip」定義を再現するために値がさらに10倍されます。価格ステップが利用できない場合は、フォールバック値0.0001が使用されます。
実装上の注意
- StockSharpはネッティングモードで動作するため、戦略はポジションレッグの内部リストを維持します。各レッグは独自のエントリー
価格、ストップ、テイクレベルを追跡し、アベレージングが元のMetaTrader EAのように動作するようにします。
- 保護注文はソフトウェアでシミュレートされます:足がストップロスまたはテイクプロフィットレベルに触れると、そのバーでポジション
が成行注文で閉じられます。
StepLossingPipsがゼロの場合、アベレージングは自動的に無効になります。そうでない場合、各追加レッグは前のレッグボリューム
にLotCoefficientを掛け、最も近いボリュームステップに切り捨てた値を使用します。
- トレーリングストップの更新は現在の価格プロキシとして足のクローズを使用します。ストップは不利な方向には動かず、価格の進歩が
TrailingStopPips + TrailingStepPipsを超えるまで非アクティブのままです。
- インジケーターバッファは
SignalBarとMaShiftオフセットを遵守するため、決定ロジックはMetaTraderエキスパートがそのインジ
ケーターバッファから得るのとまったく同じ値を見ます。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MA MACD strategy using WMA crossover with SL/TP management.
/// Buys when fast WMA crosses above slow WMA, sells on reverse cross.
/// </summary>
public class MaMacdPositionAveragingStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast WMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow WMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public MaMacdPositionAveragingStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast WMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow WMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new WeightedMovingAverage { Length = FastPeriod };
_slow = new WeightedMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// WMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 80;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 80;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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 WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_macd_position_averaging_strategy(Strategy):
"""
WMA crossover with SL/TP in price steps.
"""
def __init__(self):
super(ma_macd_position_averaging_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15).SetDisplay("Fast WMA", "Fast WMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 100).SetDisplay("Slow WMA", "Slow WMA period", "Indicators")
self._sl_points = self.Param("StopLossPoints", 200).SetDisplay("Stop Loss", "SL in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 400).SetDisplay("Take Profit", "TP in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_macd_position_averaging_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(ma_macd_position_averaging_strategy, self).OnStarted2(time)
fast = WeightedMovingAverage()
fast.Length = self._fast_period.Value
slow = WeightedMovingAverage()
slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
f = float(fast_val)
s = float(slow_val)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = f
self._prev_slow = s
return
close = float(candle.ClosePrice)
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close <= self._entry_price - self._sl_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._tp_points.Value > 0 and close >= self._entry_price + self._tp_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
elif self.Position < 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close >= self._entry_price + self._sl_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._tp_points.Value > 0 and close <= self._entry_price - self._tp_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._prev_fast <= self._prev_slow and f > s and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and f < s and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
def CreateClone(self):
return ma_macd_position_averaging_strategy()