GitHub で見る
スピードMA戦略
概要
Speed MA 戦略 は、MetaTrader 4 エキスパート アドバイザー ytg_Speed_MA_ea の直接の StockSharp 移植です。オリジナルのシステムは、単純移動平均が 1 つのバーから次のバーにどれだけ早く変化するかを測定します。移動平均の傾きがユーザー定義のしきい値を超えると、エキスパートは対応する方向に市場ポジションをオープンします。この C# 実装は、StockSharp の高レベルの API でその動作を再現します。ローソク足をサブスクライブし、シフトされた単純移動平均を評価し、連続するシフトされた値の差が十分に大きい場合に取引をトリガーします。この戦略では、注文量、利益目標、ストップロスを MetaTrader 個の「ポイント」で表現し、ソース コードに忠実に保ちます。
取引ロジック
- 構成されたローソク足タイプ (デフォルトでは 1 分足ローソク足) をサブスクライブし、
MovingAveragePeriod パラメーターを使用して単純移動平均を作成します。
- 完成したローソク足ごとに、最新の移動平均値を記録します。履歴リストには、設定された
Shift とその前のバーを評価するために必要な値のみが保持されます。
- 移動平均値
Shift バー前と 1 バー前 (つまり、Shift + 1 バー前) の値の差として傾きを計算します。これは、MetaTrader の呼び出し iMA(..., shift) と iMA(..., shift + 1) を反映しています。
- 絶対価格単位に変換された
SlopeThresholdPoints との傾きを比較します。差が正のしきい値より大きい場合は、長い信号を生成します。差が負のしきい値よりも小さい場合は、短い信号を生成します。
ReverseSignals が有効な場合、生成された信号を反転して、強気の傾斜でショート ポジションがオープンし、その逆も同様です。
- 新しい成行注文は、アクティブなポジションがない場合にのみ送信してください。元のエキスパート アドバイザーは
OrdersTotal() < 1 に依存し、直接逆転することはありませんでした。この実装は、ポジションがオープンしている間はシグナルを無視することで同様に動作します。
- 秘密保持命令は
StartProtection を通じて管理されます。ストップロスとテイクプロフィットの距離は、MetaTrader ポイント (TakeProfitPoints および StopLossPoints) で定義され、証券の小数精度を使用して価格オフセットに自動的に変換されます。
リスク管理
- ストップロス –
StopLossPoints は、エントリーの下/上に保護ストップが配置される MetaTrader ポイントの数を定義します。 0 の値はストップロスを無効にします。
- 利益確定 –
TakeProfitPoints は、利益目標距離を MetaTrader ポイントで設定します。 0 を設定すると、利益目標が無効になります。
- この戦略はストップを追跡したり、部分的な利益を得たりしません。これは、固定ターゲットを即座に設定し、注文が満たされると停止するという元の動作を再現することに重点を置いています。
- エキスパートはフラットのときにのみ新しいポジションをオープンするため、アクティブなポジションが複数存在することはありません。これにより、ポジションのサイジングが予測可能になり、ボリュームが 0.1 ロットに固定された MetaTrader の実装が反映されます。
パラメーター
| パラメータ |
説明 |
デフォルト |
OrderVolume |
市場エントリーに使用される取引量。元の EA の 0.1 ロットサイズに相当します。 |
0.1 |
MovingAveragePeriod |
速度を測定するために使用される単純移動平均の期間。 |
13 |
Shift |
現在のローソク足と移動平均サンプルの間で完了したバーの数。この戦略は、shift と shift + 1 の値を比較します。 |
1 |
SlopeThresholdPoints |
2 つのシフトされた移動平均値間の最小差 (MetaTrader ポイント単位で測定)。 |
10 |
ReverseSignals |
取引方向を反転して、強気の傾きでショートポジションをオープンします。 |
false |
TakeProfitPoints |
MetaTrader ポイントで表される利食い距離 (内部で絶対価格に変換)。 |
500 |
StopLossPoints |
MetaTrader ポイントで表されるストップロス距離 (内部で絶対価格に変換)。 |
490 |
CandleType |
計算に使用されるローソク足のタイプ (デフォルトは 1 分の時間枠)。 |
1 minute 時間枠 |
実装メモ
- MetaTrader の
Point 定数は、機器の Decimals を使用して再構築されます。 5 進数または 3 進数の外国為替シンボルの場合、コードは 1 を 10^Decimals で除算して、MetaTrader で使用されるのと同じティック値を取得します。
- 移動平均値の履歴はトリミングされ、選択した
Shift に必要なサンプルのみが保持されます。これにより、エキスパート アドバイザによって参照される正確なインデックスを尊重しながら、無制限のメモリ増大が回避されます。
StartProtection は、MetaTrader ポイントベースのパラメータを絶対価格オフセットを持つ StockSharp Unit インスタンスに変換します。これにより、ストップロスとテイクプロフィットの距離が MQL4 バージョンと同じに保たれます。
- この戦略では高レベルの
SubscribeCandles().Bind(...) ワークフローを使用するため、インジケーターの更新とシグナルの評価は終了したローソク足でのみ行われます。 Indicator.GetValue() を手動で呼び出す必要はありません。
- 重要な変換決定を強調するために、英語のインライン コメントがソース コードに提供されます。
- C# 実装のみが提供されます。 Python ポートはリクエストに合わせて意図的に省略されています。
使用のヒント
SlopeThresholdPoints を下げると、より小さな移動平均の動きがシグナルとして認められるため、取引数が増加します。値を増やすと、より多くの取引が除外され、より強い勢いが必要になります。
Shift を調整して、傾斜を測定するバーの数を変更します。 0 の値は現在の終了バーを前のバーと比較し、より高い値は移動平均の古いセクションを評価します。
- 固定ストップやターゲットを超えた追加の資金管理が必要な場合は、戦略を StockSharp リスク モジュールまたはポートフォリオ レベルのコントロールと組み合わせます。
- 購読した
CandleType が、MQL4 エキスパートを最適化するときに使用された時間枠と一致していることを確認してください。時間枠の違いにより、傾きの大きさが大幅に変わります。
元のExpert Advisorとの違い
- 市場のエントリーとエグジットでは、
OrderSend の代わりに StockSharp の成行注文ヘルパーが使用されますが、結果として生じる動作 (固定 SL/TP を持つ 1 つの成行注文) は同じままです。
- MetaTrader はチケット数を使用して注文を管理します。 StockSharp は集計位置を監視します。新しい取引を開始する前にフラット ポジションを必要とするロジックにより、新しい環境で
OrdersTotal() < 1 が再作成されます。
- ロギング、チャートの視覚化、ユニット処理で StockSharp の機能が活用されるようになり、取引の意思決定に影響を与えることなく、より適切な診断が提供されます。
ファイル
CS/SpeedMAStrategy.cs – 戦略の実装。
README.md、README_zh.md、README_ru.md – それぞれ英語、中国語、ロシア語の詳細なドキュメント。
変換ガイドラインに従って、Python ディレクトリは含まれません。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Speed MA: EMA slope breakout with ATR stops.
/// </summary>
public class SpeedMAStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevEma;
private decimal _prevPrevEma;
private decimal _entryPrice;
public SpeedMAStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 13)
.SetDisplay("EMA Length", "Moving average period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevEma = 0; _prevPrevEma = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevEma = 0; _prevPrevEma = 0; _entryPrice = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevEma == 0 || atrVal <= 0) { _prevPrevEma = _prevEma; _prevEma = emaVal; return; }
if (_prevPrevEma == 0) { _prevPrevEma = _prevEma; _prevEma = emaVal; return; }
var close = candle.ClosePrice;
var slope = emaVal - _prevEma;
var prevSlope = _prevEma - _prevPrevEma;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || slope < 0) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || slope > 0) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (slope > 0 && prevSlope <= 0) { _entryPrice = close; BuyMarket(); }
else if (slope < 0 && prevSlope >= 0) { _entryPrice = close; SellMarket(); }
}
_prevPrevEma = _prevEma; _prevEma = emaVal;
}
}
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 ExponentialMovingAverage, AverageTrueRange
class speed_ma_strategy(Strategy):
def __init__(self):
super(speed_ma_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 13) \
.SetDisplay("EMA Length", "Moving average period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_ema = 0.0
self._prev_prev_ema = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(speed_ma_strategy, self).OnStarted2(time)
self._prev_ema = 0.0
self._prev_prev_ema = 0.0
self._entry_price = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
av = float(atr_val)
if self._prev_ema == 0 or av <= 0:
self._prev_prev_ema = self._prev_ema
self._prev_ema = ev
return
if self._prev_prev_ema == 0:
self._prev_prev_ema = self._prev_ema
self._prev_ema = ev
return
close = float(candle.ClosePrice)
slope = ev - self._prev_ema
prev_slope = self._prev_ema - self._prev_prev_ema
if self.Position > 0:
if close >= self._entry_price + av * 2.5 or close <= self._entry_price - av * 1.5 or slope < 0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 2.5 or close >= self._entry_price + av * 1.5 or slope > 0:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_prev_ema = self._prev_ema
self._prev_ema = ev
return
if self.Position == 0:
if slope > 0 and prev_slope <= 0:
self._entry_price = close
self.BuyMarket()
elif slope < 0 and prev_slope >= 0:
self._entry_price = close
self.SellMarket()
self._prev_prev_ema = self._prev_ema
self._prev_ema = ev
def OnReseted(self):
super(speed_ma_strategy, self).OnReseted()
self._prev_ema = 0.0
self._prev_prev_ema = 0.0
self._entry_price = 0.0
def CreateClone(self):
return speed_ma_strategy()