5/8 MAクロス戦略
概要
5/8 MAクロス戦略は、MetaTraderのエキスパートアドバイザー「5_8 MACross」のStockSharpへのポートです。終値で計算された高速指数移動平均(EMA)と始値で計算された低速EMAを比較します。システムは2つの移動平均のクロスオーバーで動作し、標準的な時間ベースのローソク足を提供する任意のシンボルと時間軸に適用できます。
インジケーター
- 高速EMA – 設定可能な長さ(デフォルト5)、ローソク足の終値から計算。
- 低速EMA – 設定可能な長さ(デフォルト8)、ローソク足の始値から計算。
トレードロジック
- 戦略は部分的なデータを避けるために完了したローソク足のみを処理します。
- 高速EMAが前のローソク足で低速EMAより下または等しく、現在のローソク足でその上にクロスした場合にロングエントリーが生成されます。
- 高速EMAが前のローソク足で低速EMAより上または等しく、現在のローソク足でその下にクロスした場合にショートエントリーが生成されます。
- シグナルが現れると、戦略はエクスポージャーを反転します:オープンポジションを閉じ、新しい方向で
Volumeコントラクトとなるようにサイズを調整した成行注文を送信します。
リスク管理
- テイクプロフィット – 価格ポイントで表されるオプションの目標。ポイントサイズはインストゥルメントの価格ステップから導出され、3桁と5桁の気配値ではMetaTraderのpip処理をエミュレートするために値が自動的に10倍されます。
- ストップロス – エントリー価格から価格ポイントで表されるオプションの保護ストップ。
- トレーリングストップ – 価格ポイントのオプションの距離。ポジションが開かれた後、戦略は最高値(ロング用)または最安値(ショート用)を追跡し、利益方向にのみストップを移動します。初期ストップロスが指定されていない場合でも、トレーリングストップはエントリー直後に保護を開始します。
- テイクプロフィットまたは(トレーリング)ストップが終値で達成された場合、ポジションは成行で決済されます。
パラメーター
| 名前 | 説明 | デフォルト値 |
|---|---|---|
FastLength |
高速EMA(終値ベース)の期間。 | 5 |
SlowLength |
低速EMA(始値ベース)の期間。 | 8 |
TakeProfitPoints |
価格ポイントでのテイクプロフィット距離。 | 40 |
StopLossPoints |
価格ポイントでのストップロス距離(0はストップを無効化)。 | 0 |
TrailingStopPoints |
価格ポイントでのトレーリングストップ距離(0はトレーリングを無効化)。 | 0 |
CandleType |
計算に使用するローソク足タイプ/時間軸。 | 1分の時間軸 |
Volume |
基底クラスStrategyから継承した注文ボリューム。 |
0.1 |
MQLバージョンとの違い
- StockSharpはポジション会計を異なる方法で処理するため、MetaTrader固有のヘッジングチェックとアカウント情報呼び出しは省略されています。
- シグナルは新しいバーの最初のティックではなく、閉じたローソク足で評価されます。これにより、イベント駆動型環境での安定性が向上します。
- トレーリングロジックは現在のbid/askティックの代わりにローソク足の高値/安値を使ってストップを進め、履歴処理に対して決定論的な動作を提供します。
使用上の注意
- 希望のロットサイズに合わせて戦略プロパティで
Volumeを設定します。 - ポートフォリオレベルのリスク管理が必要な場合は、StockSharpの保護モジュールや追加フィルターと組み合わせます。
- 戦略は指値注文を発注しません。すべてのエントリーと決済は、上記のクロスオーバーとリスクロジックによって生成された成行注文で実行されます。
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>
/// 5/8 exponential moving average crossover strategy converted from MetaTrader.
/// Uses a fast EMA on close prices and a slower EMA on open prices with manual stop, take profit, and trailing logic.
/// </summary>
public class FiveEightMaCrossStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastMa = null!;
private ExponentialMovingAverage _slowMa = null!;
private decimal _prevFast;
private decimal _prevSlow;
private bool _isInitialized;
private decimal _pointValue;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _trailDistance;
private decimal _maxPrice;
private decimal _minPrice;
/// <summary>
/// Fast EMA length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow EMA length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Take profit distance expressed in price points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in price points.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="FiveEightMaCrossStrategy"/>.
/// </summary>
public FiveEightMaCrossStrategy()
{
_fastLength = Param(nameof(FastLength), 8)
.SetGreaterThanZero()
.SetDisplay("Fast EMA Length", "Length of the EMA calculated on closing prices", "Indicators")
.SetOptimize(3, 20, 1);
_slowLength = Param(nameof(SlowLength), 21)
.SetGreaterThanZero()
.SetDisplay("Slow EMA Length", "Length of the EMA calculated on opening prices", "Indicators")
.SetOptimize(5, 30, 1);
_takeProfitPoints = Param(nameof(TakeProfitPoints), 40m)
.SetDisplay("Take Profit (points)", "Take profit distance expressed in price points", "Risk Management")
.SetNotNegative()
.SetOptimize(10m, 100m, 10m);
_stopLossPoints = Param(nameof(StopLossPoints), 0m)
.SetDisplay("Stop Loss (points)", "Stop loss distance expressed in price points", "Risk Management")
.SetNotNegative()
.SetOptimize(0m, 100m, 10m);
_trailingStopPoints = Param(nameof(TrailingStopPoints), 0m)
.SetDisplay("Trailing Stop (points)", "Trailing stop distance expressed in price points", "Risk Management")
.SetNotNegative()
.SetOptimize(0m, 100m, 10m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle type used for calculations", "General");
Volume = 0.1m;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastMa = null!;
_slowMa = null!;
_prevFast = 0m;
_prevSlow = 0m;
_isInitialized = false;
_pointValue = 0m;
ResetPositionState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastMa = new EMA { Length = FastLength };
_slowMa = new EMA { Length = SlowLength };
_pointValue = CalculatePointValue();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private decimal CalculatePointValue()
{
var step = Security?.PriceStep;
if (step == null || step.Value <= 0m)
return 1m;
var stepValue = step.Value;
var stepDouble = (double)stepValue;
if (stepDouble <= 0d)
return stepValue;
var digitsDouble = Math.Log10(1d / stepDouble);
var digits = (int)Math.Round(digitsDouble, MidpointRounding.AwayFromZero);
var multiplier = (digits == 3 || digits == 5) ? 10m : 1m;
return stepValue * multiplier;
}
private void ProcessCandle(ICandleMessage candle)
{
// Process only finished candles to avoid repainting.
if (candle.State != CandleStates.Finished)
return;
// Feed indicators with the corresponding price source.
var fastValue = _fastMa.Process(new DecimalIndicatorValue(_fastMa, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
var slowValue = _slowMa.Process(new DecimalIndicatorValue(_slowMa, candle.OpenPrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (!_fastMa.IsFormed || !_slowMa.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
HandleRiskManagement(candle);
// indicators are processed manually
if (!_isInitialized)
{
_prevFast = fastValue;
_prevSlow = slowValue;
_isInitialized = true;
return;
}
var crossUp = _prevFast <= _prevSlow && fastValue > slowValue;
var crossDown = _prevFast >= _prevSlow && fastValue < slowValue;
if (crossUp && Position <= 0)
{
EnterLong(candle);
}
else if (crossDown && Position >= 0)
{
EnterShort(candle);
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
private void EnterLong(ICandleMessage candle)
{
var positionVolume = Position < 0 ? Math.Abs(Position) : 0m;
var volume = Volume + positionVolume;
if (volume <= 0m)
return;
ResetPositionState();
// Enter long position with volume including any short covering.
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
_takePrice = TakeProfitPoints > 0m ? _entryPrice + TakeProfitPoints * _pointValue : null;
_stopPrice = StopLossPoints > 0m ? _entryPrice - StopLossPoints * _pointValue : null;
_trailDistance = TrailingStopPoints > 0m ? TrailingStopPoints * _pointValue : 0m;
_maxPrice = candle.HighPrice;
_minPrice = candle.LowPrice;
if (_trailDistance > 0m)
{
var trailStart = _entryPrice.Value - _trailDistance;
if (_stopPrice == null || trailStart > _stopPrice.Value)
_stopPrice = trailStart;
}
}
private void EnterShort(ICandleMessage candle)
{
var positionVolume = Position > 0 ? Position : 0m;
var volume = Volume + positionVolume;
if (volume <= 0m)
return;
ResetPositionState();
// Enter short position with volume including any long exit.
SellMarket(volume);
_entryPrice = candle.ClosePrice;
_takePrice = TakeProfitPoints > 0m ? _entryPrice - TakeProfitPoints * _pointValue : null;
_stopPrice = StopLossPoints > 0m ? _entryPrice + StopLossPoints * _pointValue : null;
_trailDistance = TrailingStopPoints > 0m ? TrailingStopPoints * _pointValue : 0m;
_maxPrice = candle.HighPrice;
_minPrice = candle.LowPrice;
if (_trailDistance > 0m)
{
var trailStart = _entryPrice.Value + _trailDistance;
if (_stopPrice == null || trailStart < _stopPrice.Value)
_stopPrice = trailStart;
}
}
private void HandleRiskManagement(ICandleMessage candle)
{
if (Position > 0 && _entryPrice.HasValue)
{
// Update trailing stop using the highest price reached since entry.
_maxPrice = Math.Max(_maxPrice, candle.HighPrice);
if (_trailDistance > 0m)
{
var trailCandidate = _maxPrice - _trailDistance;
if (_stopPrice == null || trailCandidate > _stopPrice.Value)
_stopPrice = trailCandidate;
}
if (_takePrice.HasValue && candle.ClosePrice >= _takePrice.Value)
{
SellMarket(Math.Abs(Position));
ResetPositionState();
return;
}
if (_stopPrice.HasValue && candle.ClosePrice <= _stopPrice.Value)
{
SellMarket(Math.Abs(Position));
ResetPositionState();
return;
}
}
else if (Position < 0 && _entryPrice.HasValue)
{
// Update trailing stop using the lowest price reached since entry.
_minPrice = Math.Min(_minPrice, candle.LowPrice);
if (_trailDistance > 0m)
{
var trailCandidate = _minPrice + _trailDistance;
if (_stopPrice == null || trailCandidate < _stopPrice.Value)
_stopPrice = trailCandidate;
}
if (_takePrice.HasValue && candle.ClosePrice <= _takePrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
return;
}
if (_stopPrice.HasValue && candle.ClosePrice >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
}
}
}
private void ResetPositionState()
{
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
_trailDistance = 0m;
_maxPrice = 0m;
_minPrice = 0m;
}
}
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 System import Decimal
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class five_eight_ma_cross_strategy(Strategy):
def __init__(self):
super(five_eight_ma_cross_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 8)
self._slow_length = self.Param("SlowLength", 21)
self._take_profit_points = self.Param("TakeProfitPoints", 40.0)
self._stop_loss_points = self.Param("StopLossPoints", 0.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 0.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_initialized = False
self._point_value = 1.0
self._entry_price = None
self._stop_price = None
self._take_price = None
self._trail_distance = 0.0
self._max_price = 0.0
self._min_price = 0.0
@property
def FastLength(self):
return self._fast_length.Value
@FastLength.setter
def FastLength(self, value):
self._fast_length.Value = value
@property
def SlowLength(self):
return self._slow_length.Value
@SlowLength.setter
def SlowLength(self, value):
self._slow_length.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@TrailingStopPoints.setter
def TrailingStopPoints(self, value):
self._trailing_stop_points.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(five_eight_ma_cross_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_initialized = False
self._point_value = self._calculate_point_value()
self._reset_position_state()
self._fast_ma = ExponentialMovingAverage()
self._fast_ma.Length = self.FastLength
self._slow_ma = ExponentialMovingAverage()
self._slow_ma.Length = self.SlowLength
self.Volume = 0.1
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def _calculate_point_value(self):
if self.Security is None or self.Security.PriceStep is None:
return 1.0
step = float(self.Security.PriceStep)
if step <= 0.0:
return 1.0
import math
digits = int(round(math.log10(1.0 / step)))
multiplier = 10.0 if (digits == 3 or digits == 5) else 1.0
return step * multiplier
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
open_price = float(candle.OpenPrice)
fast_result = process_float(self._fast_ma, candle.ClosePrice, candle.OpenTime, True)
slow_result = process_float(self._slow_ma, candle.OpenPrice, candle.OpenTime, True)
fast_val = fast_result.Value
slow_val = slow_result.Value
if not self._fast_ma.IsFormed or not self._slow_ma.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
self._handle_risk_management(candle)
if not self._is_initialized:
self._prev_fast = fast_val
self._prev_slow = slow_val
self._is_initialized = True
return
cross_up = self._prev_fast <= self._prev_slow and fast_val > slow_val
cross_down = self._prev_fast >= self._prev_slow and fast_val < slow_val
if cross_up and self.Position <= 0:
self._enter_long(candle)
elif cross_down and self.Position >= 0:
self._enter_short(candle)
self._prev_fast = fast_val
self._prev_slow = slow_val
def _enter_long(self, candle):
pos_vol = abs(float(self.Position)) if self.Position < 0 else 0.0
volume = float(self.Volume) + pos_vol
if volume <= 0:
return
self._reset_position_state()
self.BuyMarket(volume)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
tp_pts = float(self.TakeProfitPoints)
sl_pts = float(self.StopLossPoints)
trail_pts = float(self.TrailingStopPoints)
self._entry_price = close
self._take_price = close + tp_pts * self._point_value if tp_pts > 0.0 else None
self._stop_price = close - sl_pts * self._point_value if sl_pts > 0.0 else None
self._trail_distance = trail_pts * self._point_value if trail_pts > 0.0 else 0.0
self._max_price = high
self._min_price = low
if self._trail_distance > 0.0:
trail_start = close - self._trail_distance
if self._stop_price is None or trail_start > self._stop_price:
self._stop_price = trail_start
def _enter_short(self, candle):
pos_vol = float(self.Position) if self.Position > 0 else 0.0
volume = float(self.Volume) + pos_vol
if volume <= 0:
return
self._reset_position_state()
self.SellMarket(volume)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
tp_pts = float(self.TakeProfitPoints)
sl_pts = float(self.StopLossPoints)
trail_pts = float(self.TrailingStopPoints)
self._entry_price = close
self._take_price = close - tp_pts * self._point_value if tp_pts > 0.0 else None
self._stop_price = close + sl_pts * self._point_value if sl_pts > 0.0 else None
self._trail_distance = trail_pts * self._point_value if trail_pts > 0.0 else 0.0
self._max_price = high
self._min_price = low
if self._trail_distance > 0.0:
trail_start = close + self._trail_distance
if self._stop_price is None or trail_start < self._stop_price:
self._stop_price = trail_start
def _handle_risk_management(self, candle):
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0 and self._entry_price is not None:
self._max_price = max(self._max_price, high)
if self._trail_distance > 0.0:
trail_candidate = self._max_price - self._trail_distance
if self._stop_price is None or trail_candidate > self._stop_price:
self._stop_price = trail_candidate
if self._take_price is not None and close >= self._take_price:
self.SellMarket(abs(float(self.Position)))
self._reset_position_state()
return
if self._stop_price is not None and close <= self._stop_price:
self.SellMarket(abs(float(self.Position)))
self._reset_position_state()
return
elif self.Position < 0 and self._entry_price is not None:
self._min_price = min(self._min_price, low)
if self._trail_distance > 0.0:
trail_candidate = self._min_price + self._trail_distance
if self._stop_price is None or trail_candidate < self._stop_price:
self._stop_price = trail_candidate
if self._take_price is not None and close <= self._take_price:
self.BuyMarket(abs(float(self.Position)))
self._reset_position_state()
return
if self._stop_price is not None and close >= self._stop_price:
self.BuyMarket(abs(float(self.Position)))
self._reset_position_state()
def _reset_position_state(self):
self._entry_price = None
self._stop_price = None
self._take_price = None
self._trail_distance = 0.0
self._max_price = 0.0
self._min_price = 0.0
def OnReseted(self):
super(five_eight_ma_cross_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_initialized = False
self._point_value = 1.0
self._reset_position_state()
def CreateClone(self):
return five_eight_ma_cross_strategy()