GitHub で見る
シグナル戦略による平均化
概要
信号による平均化戦略 は、MetaTrader エキスパート AveragingBySignal.mq4 を StockSharp のハイレベル API に移植します。元のアドバイザーは、移動平均クロスオーバー エントリー フィルターと、Martingale スタイルの平均化、共有バスケットのテイクプロフィット、および最初の注文に対してのみ有効になるオプションのトレーリング ストップを組み合わせていました。この C# バージョンは、同じ構成要素を再作成しながら、StockSharp のネッティング実行モデルとインジケーター フレームワークに適応させます。
取引ロジック
- 構成された時間枠 (
CandleType) をサブスクライブし、要求された期間とメソッド (FastPeriod/FastMethod、SlowPeriod/SlowMethod) で構築された 2 つの移動平均をフィードします。
- キャンドルが完全に閉じられるまで待ちます。バーが完了したら、両方の平均の前の値と現在の値を比較して、速い/遅いクロスオーバーを検出します。
- シグナルを生成します。
- 強気のクロスオーバー(低速を上回る高速上昇)は長いシグナルを生成します。
- 弱気のクロスオーバー(低速を下回る高速低下)は短いシグナルを生成します。
- それ以外の場合、戦略はアイドル状態のままになります。
- 新しいロングシグナルで、ロングバスケットがアクティブでない間に、ポジションサイジングブロックによって返された基本ボリュームを使用して成行買い注文を送信します。
- 新しいショートシグナルで、アクティブなショートバスケットがない間に成行売り注文を送信します。
- 平均化ルール:
- 次のレイヤーまでの距離は、MetaTrader スタイルのピップに変換された
LayerDistancePips によって制御されます。
- 追加のロングレイヤーには、強気シグナル (
AveragingBySignal が true の場合) または価格条件のみ (false の場合) が必要です。
- 追加の短い層は対称ロジックに従います。
- 新しい各レイヤーのロット サイズは
LotSizing モードで計算され、方向ごとに MaxLayers エントリに制限されます。
- バスケット管理:
- 満たされたすべての取引は、ロングバスケットとショートバスケットの平均エントリー価格を再構築するためにFIFOで追跡されます。
- 加重平均価格プラス/マイナス
TakeProfitPips が共有テイクプロフィットを形成します。終値がそのレベルに達すると、バスケット全体が閉じられます。
EnableTrailing が有効で、バスケットに注文が 1 つだけ存在する場合、浮動利益の TrailingStartPips の後にトレーリング ストップが設定されます。価格が少なくとも TrailingStepPips 改善するたびにストップが繰り上げられます。
- この戦略はネッティング環境で機能します。次のバスケットを開く前に、反対のシグナルが既存のエクスポージャを自動的に相殺します。
ポジションサイジングとピップ計算
InitialVolume は基本ロットを定義します。 LotSizing が Multiplier に設定されている場合、追加の各レイヤーは基本ロットに Multiplier^layerIndex を乗算し、MQL LotType ロジックを再現します。
- ヘルパーは、リクエストされたボリュームを楽器の
VolumeStep、MinVolume、および MaxVolume に合わせて調整し、すべての注文が取引所に準拠するようにします。
- Pip 値は
Security.PriceStep から導出され、元の「2 桁」調整を模倣します。5 桁の FX シンボルは 0.0001 を使用しますが、4 桁のシンボルは 0.0001 をそのまま使用します。
パラメーター
| 名前 |
種類 |
デフォルト |
説明 |
CandleType |
DataType |
1時間枠 |
インジケーター計算の主要な時間枠。 |
InitialVolume |
decimal |
0.1 |
バスケット内の最初の注文の基本ロット サイズ。 |
LotSizing |
LotSizingMode |
Multiplier |
固定ロットまたは幾何学的スケーリングのいずれかを選択します。 |
Multiplier |
decimal |
2 |
LotSizing = Multiplier の場合、ロット乗数がすべての追加レイヤーに適用されます。 |
FastPeriod |
int |
28 |
高速移動平均の振り返り。 |
FastMethod |
MovingAverageMethod |
LinearWeighted |
高速ラインの移動平均法。 |
SlowPeriod |
int |
50 |
遅い移動平均の振り返り。 |
SlowMethod |
MovingAverageMethod |
Smoothed |
遅いラインの移動平均法。 |
TakeProfitPips |
int |
15 |
バスケット全体の共有テイプロフィットディスタンス (0 無効)。 |
AveragingBySignal |
bool |
true |
新しいレイヤーを追加する前に、新しい信号が必要です。 |
LayerDistancePips |
decimal |
10 |
平均化前の最小の逆方向の動き (ピップ単位)。 |
MaxLayers |
int |
10 |
最初のものも含め、方向ごとの最大同時注文数。 |
EnableTrailing |
bool |
false |
単一注文バスケットのトレーリングストップを有効にします。 |
TrailingStartPips |
decimal |
10 |
トレーリングを開始する前に変動利益が必要です。 |
TrailingStepPips |
decimal |
1 |
トレーリングストップを移動するには追加の進捗が必要です。 |
元のエキスパートアドバイザーとの違い
- StockSharp はネッティング モードで動作しますが、MetaTrader 4 では独立したヘッジ ポジションが可能でした。シグナルの方向が反転すると、新しい成行注文が既存のエクスポージャーを相殺してから、新しいバスケットが作成されます。
- 共有テイクプロフィットは、各チケットを
OrderModify で変更するのではなく、明示的な終了コマンドとして実装されます。
- トレーリングストップは、ローソク足の終値によって引き起こされる市場の出口でモデル化されています。元の専門家はティックレベルのストップ更新に依存していました。したがって、C# バージョンはわずかに遅れる可能性がありますが、同じしきい値に従います。
- StockSharp ブローカーは証拠金/価格ルールを直接適用するため、
AccountFreeMarginCheck やスリッページ処理などのリスク チェックは省略されます。
使い方のヒント
- 正しいピップとボリュームの変換のために、正確な楽器メタデータ (
PriceStep、VolumeStep、最小および最大ボリューム) を提供します。
FastPeriod を SlowPeriod よりも厳密に小さくしてください。構成によって有効なクロスオーバーが妨げられる場合、戦略は自動的に停止します。
- 最新のクロスオーバーに関係なく、価格レベルのみに反応する純粋なグリッドが必要な場合は、
AveragingBySignal を無効にします。
- 終了ロジックは閉じたローソク足で動作するため、タイムフレームが低いほど反応が速くなりますが、ノイズと平均化レイヤーの数も増加する可能性があります。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Averaging By Signal strategy: WMA crossover with averaging logic.
/// Buys when fast WMA crosses above slow WMA, sells on cross below.
/// </summary>
public class AveragingBySignalStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public AveragingBySignalStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast WMA", "Fast WMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow WMA", "Slow WMA period", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_hasPrev = false;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (_hasPrev)
{
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
BuyMarket();
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
SellMarket();
}
else
{
if (fastValue > slowValue && Position <= 0)
BuyMarket();
else if (fastValue < slowValue && Position >= 0)
SellMarket();
}
_prevFast = fastValue;
_prevSlow = slowValue;
_hasPrev = 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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class averaging_by_signal_strategy(Strategy):
def __init__(self):
super(averaging_by_signal_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._fast_period = self.Param("FastPeriod", 10)
self._slow_period = self.Param("SlowPeriod", 30)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastPeriod(self):
return self._fast_period.Value
@FastPeriod.setter
def FastPeriod(self, value):
self._fast_period.Value = value
@property
def SlowPeriod(self):
return self._slow_period.Value
@SlowPeriod.setter
def SlowPeriod(self, value):
self._slow_period.Value = value
def OnReseted(self):
super(averaging_by_signal_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(averaging_by_signal_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._process_candle).Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if self._has_prev:
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
self.SellMarket()
else:
if fast_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif fast_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
self._has_prev = True
def CreateClone(self):
return averaging_by_signal_strategy()