GitHub で見る
15分間スキャルパー戦略
この戦略は、MetaTraderの15M ScalperエキスパートアドバイザーをStockSharpの高レベルAPIに移植したものです。マルチフィルター
エントリーロジック(加重移動平均、ストキャスティクスオシレーター、Parabolic SAR、マルチタイムフレームモメンタム、月次MACD)と、
金銭ベースのターゲット、トレーリングストップ、ブレイクイーブン移動、資本ドローダウン保護を組み合わせた豊富な決済スタックを
再現しています。StockSharpバージョンはEAと同様に完成した足で動作し、元のパラメーターを保持しながらイベント駆動型コードを維持します。
動作原理
- トレンドフィルター – 現在の時間軸(デフォルト15分)で計算された高速・低速の加重移動平均が取引方向に合致している
必要があります。平均はMQLの
PRICE_TYPICAL入力に合わせるために典型的価格((High + Low + Close) / 3)を使用します。
- ストキャスティクス反転 – 5/3/3ストキャスティクスオシレーターが前の2本の閉じた足でサンプリングされます。ロングシグナル
は%Kが20を上回るクロスバックが必要で、ショートは80を下回るクロスが必要で、スクリプトの
Stoc1/Stoc2チェックを反映します。
- Parabolic SAR確認 – 完成したバーのSAR値はロングでは前回の始値より下にあり、ショートでは上にある必要があり、安全フィルター
SAR < Open[1] / SAR > Open[1]を再現します。
- 上位タイムフレームモメンタム – 設定可能な上位タイムフレーム(デフォルト1時間)の14期間モメンタム指標が、最後の3本の
閉じたバーのいずれかで少なくとも売買閾値分だけ100から乖離する必要があります。これはインジケーターバッファに直接アクセスせずに
MomLevelB/MomLevelSトリオを実装します。
- 月次MACD – 月次足ストリーム(デフォルト30日バー)のMACDシリーズは、ロングでは主線がシグナルより上にあり、ショートでは
下にあることを維持します。同じMACDフィルターは、ラインが反対方向にクロスしたときにポジションを閉じるオプションの決済ロジック
も駆動します。
- 注文処理 – 反対のセットアップが現れると、戦略はまず既存のポジションをフラット化し、その後次のバーで新しい方向の取引を
開くまで待機します。ボリュームスケーリングは
LotExponentとロス感応型IncreaseFactorを介してEAのマーチンゲールルールに従います。
リスク管理
- ストップロス / テイクプロフィット – 距離はMetaTraderの「ポイント」で入力され、
Security.PriceStepを通じて絶対価格に
変換されます。分数FXティック(価格ステップ < 1)の場合、実装はEAのpip処理を模倣するためにステップを10倍します。
- ブレイクイーブン(「ノーロス」) – 価格が
BreakEvenTriggerSteps移動すると、ストップは仮想的にエントリーに設定された
オフセットを加えた位置に移動します。価格がそのレベルを逆行した場合、ポジションは成行で決済されます。
- トレーリングストップ – 足ベースのトレーリングストップはロングの最高値またはショートの最安値を監視します。反落が
TrailingStopStepsを超えると、ポジションが決済され、元のOrderModify動作を複製します。
- 金銭ターゲット –
UseProfitTargetMoney、UseProfitTargetPercent、EnableMoneyTrailingはPriceStep × StepPriceで
測定された浮動P&Lで動作します。ポートはテイクプロフィット、パーセンテージターゲット、トレーリングドローダウン(MoneyTrailingStop)
ロジックをそのまま維持します。
- 資本ストップ –
UseEquityStopは(初期資本 + 実現P&L + 浮動利益)のピークを追跡します。現在のドローダウンがそのピークの
TotalEquityRiskパーセントを超えると、すべてのポジションが決済され、EAのAccountEquityHigh()とTotalEquityRiskを複製します。
- マーチンゲールサイジング – 同方向の各追加取引は
LotExponentでボリュームをスケーリングします。連続損失は次のベースボリュームを
損失ごとにIncreaseFactorずつ増加させ、MQLIncreaseFactorブランチと同じ「適応型」ロットサイジングを提供します。
パラメーター
| パラメーター |
説明 |
CandleType |
主要な作業時間軸(デフォルト15分足)。 |
MomentumCandleType |
モメンタムフィルター用の上位時間軸(デフォルト1時間足)。 |
MacdCandleType |
MACDトレンドフィルター用の時間軸(デフォルト30日足)。 |
FastMaPeriod, SlowMaPeriod |
トレンドフィルターを定義する加重移動平均の期間。 |
MomentumPeriod |
上位時間軸のモメンタム期間。 |
MomentumBuyThreshold, MomentumSellThreshold |
ロング/ショート取引を許可するために必要な100からの最小絶対乖離。 |
StopLossSteps, TakeProfitSteps |
価格ステップ単位のプロテクティブストップとターゲット距離。無効化するにはゼロに設定。 |
TrailingStopSteps |
価格ステップ単位のトレーリングストップ距離。 |
UseMoveToBreakeven, BreakEvenTriggerSteps, BreakEvenOffsetSteps |
ブレイクイーブン有効化フラグ、トリガー距離、オフセット。 |
UseProfitTargetMoney, ProfitTargetMoney |
金銭ベースの浮動利益ターゲットを有効化して設定。 |
UseProfitTargetPercent, ProfitTargetPercent |
パーセンテージベースの浮動利益ターゲットを有効化して設定。 |
EnableMoneyTrailing, MoneyTrailingTakeProfit, MoneyTrailingStop |
マネートレーリングのトリガーと口座通貨での最大許容反落。 |
UseEquityStop, TotalEquityRisk |
資本ドローダウン制御を有効化し、ピーク資本の許容パーセンテージを設定。 |
BaseVolume, LotExponent, IncreaseFactor, MaxTrades |
マーチンゲールサイジングオプション:初期ロット、乗数、損失ベースの増分、最大追加数。 |
UseExitByMacd |
MACDメインラインが取引に反してシグナルをクロスしたときにポジションを決済。 |
使用方法
- 戦略を銘柄に接続し、
Security.PriceStepとSecurity.StepPriceが入力されていることを確認します。これらの値はpipベースの
入力と金銭ターゲットを絶対数に変換するために使用されます。
- 異なる時間軸でスキャルパーを実行したい場合は
CandleType、MomentumCandleType、MacdCandleTypeを調整します。デフォルトは
元の15分/1時間/月次設定を複製します。
- pipベースの距離(
StopLossSteps、TakeProfitSteps、TrailingStopSteps、ブレイクイーブン設定)を銘柄のティックサイズに
合わせて調整します。提供されたデフォルト値から始め、より不安定な市場では増加させてください。
- 資金管理設定を行います:金銭またはパーセンテージのテイクプロフィットを使用するかどうか決め、マネートレーリングを有効化し、
深いドローダウンに対するセーフティネットが必要な場合は資本ストップを設定します。
- 戦略を起動します。必要なすべての足データストリームに自動的にサブスクライブし、インジケーターをプロット(チャートが利用可能な
場合)し、各インジケーターに十分な履歴ができ次第シグナルの評価を開始します。
注意点と元のEAとの違い
- ポートはStockSharpの集約ポジションモデルを使用します。反対のシグナルが現れると、現在のポジションが先に決済され、新しい方向が
次の足で評価され、動作を決定論的に保ちます。
- 金銭ベースの計算は
Security.PriceStepとSecurity.StepPriceに依存します。取引場所がこれらの値を提供しない場合、金銭ターゲット
はスキップされます(浮動利益はゼロとして報告)、コードコメントに記載の通りです。
IncreaseFactorは自由証拠金を使用する代わりに(サンドボックス環境では利用不可)、IncreaseFactor × 連続損失数を次のベース
ボリュームに追加します。これでも損失連続後にサイズを拡大する意図を捉えています。
- すべての決定は完成した足で行われ、シグナルの二重カウントを回避し、MetaTrader実装のバーごとのチェックに合致します。
- ビジュアライザーが利用可能な場合、戦略はチャートに同じインジケーターを描き、デバッグを支援し、ポートをEAと比較しやすくします。
本番取引前に、ブローカーのティックサイズ、ステップ価格、ボリューム制約を慎重に確認してください。これらの値は戦略内でpipベースの
距離と金銭ターゲットがどのように変換されるかに直接影響します。
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>
/// 15-minute scalper strategy using WMA crossover with ParabolicSar confirmation.
/// Buys when fast WMA crosses above slow WMA and SAR is below price.
/// Sells on reverse conditions.
/// </summary>
public class FifteenMinuteScalperStrategy : 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 FifteenMinuteScalperStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast WMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 85)
.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 fifteen_minute_scalper_strategy(Strategy):
"""
15-minute scalper using WMA crossover with SL/TP and cooldown.
Buys when fast WMA crosses above slow WMA, sells on reverse.
"""
def __init__(self):
super(fifteen_minute_scalper_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 20) \
.SetDisplay("Fast Period", "Fast WMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 85) \
.SetDisplay("Slow Period", "Slow WMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Trading timeframe", "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(fifteen_minute_scalper_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(fifteen_minute_scalper_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
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_fast = float(fast_val)
self._prev_slow = float(slow_val)
return
fast_val = float(fast_val)
slow_val = float(slow_val)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
sl_pts = self._stop_loss_points.Value
tp_pts = self._take_profit_points.Value
if self.Position > 0 and self._entry_price > 0:
if sl_pts > 0 and close <= self._entry_price - sl_pts * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if tp_pts > 0 and close >= self._entry_price + tp_pts * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if sl_pts > 0 and close >= self._entry_price + sl_pts * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if tp_pts > 0 and close <= self._entry_price - tp_pts * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val 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 fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return fifteen_minute_scalper_strategy()