EMA 6/12 クロスオーバー戦略
この戦略は、高速な EMA(6) と低速な EMA(12) の間のクロスオーバーをトレードする MetaTrader エキスパート アドバイザーを複製します。デフォルトで時間足のローソク足をサブスクライブし、両方の移動平均を計算し、ローソク足の終値で確認されたクロスオーバーを待ってから行動します。
取引ロジック
- エントリー:
- EMA(6)がEMA(12)を上抜けたときに強気シグナルが現れます。アクティブなポジションがない場合、この戦略はロング ポジションをオープンします。
- EMA(6)がEMA(12)を下回ると弱気シグナルが現れます。アクティブなポジションがない場合、ストラテジーはショート ポジションをオープンします。
- 出口:
UseCloseSignalsが有効になっている場合 (デフォルトの動作)、反対側のクロスオーバーが検出されると、ストラテジーは現在のポジションを閉じます。元のエキスパートアドバイザーを反映して、新しい取引を開始する前に次のクロスオーバーを待ちます。- オプションのテイクプロフィットとトレーリングストップ保護は、StockSharp の組み込み
StartProtectionヘルパーを介して管理されます。
- ポジションサイジング:
- 注文では
OrderVolumeパラメータを使用します (デフォルトは 1 ロット)。ボリュームは、注文を送信する前にセキュリティ設定に合わせて調整されます。
- 注文では
リスク管理
- トレーリングストップ: 元の「ポイント」設定を価格ステップに変換します。ゼロより大きい場合、ポジションが利益を得るようになると、ストップは自動的に取引の方向に追従します。
- 利益確定: 価格ステップで表されます。無効にするには、ゼロに設定します。
- この戦略は決して平均値を下げたり、ピラミッドにしたりすることはありません。シンボルごとに 1 つの位置のみが許可されます。
パラメーター
| パラメータ | 説明 |
|---|---|
CandleType |
ローソク足とEMAを構築するために使用される時間枠。デフォルトは 1 時間です。 |
OrderVolume |
サイズをロット単位で取引します。 |
ShortEmaLength |
高速 EMA の期間 (デフォルトは 6)。 |
LongEmaLength |
低速の期間 EMA (デフォルトは 12)。 |
UseCloseSignals |
反対側のクロスオーバーの現在位置を閉じます (デフォルト: 有効)。 |
TrailingStopSteps |
価格ステップにおけるトレーリング距離。ゼロは末尾を無効にします。 |
TakeProfitSteps |
価格ステップでの利益確定距離。ゼロはそれを無効にします。 |
注意事項
- バー内のノイズを避けるために、信号は完成したキャンドルでのみ処理されます。
- 決済後も直前の EMA 値を保持し、新しいクロスだけを検出します。決済のためのクロスで同じ足に反対方向のポジションを開くことはありません。
- コードのコメントはすべて英語で書かれており、インデントはプロジェクトのガイドラインに従ってタブを使用しています。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// EMA(6)/EMA(12) crossover: enter while flat and close (never reverse on the
/// same crossover) when the close-signals switch is enabled.
/// </summary>
public class Ema612Strategy : Strategy
{
private readonly StrategyParam<int> _shortEmaLength;
private readonly StrategyParam<int> _longEmaLength;
private readonly StrategyParam<bool> _useCloseSignals;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _trailingStopSteps;
private readonly StrategyParam<decimal> _takeProfitSteps;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousFast;
private decimal? _previousSlow;
private Order _pendingOrder;
public int ShortEmaLength { get => _shortEmaLength.Value; set => _shortEmaLength.Value = value; }
public int LongEmaLength { get => _longEmaLength.Value; set => _longEmaLength.Value = value; }
public bool UseCloseSignals { get => _useCloseSignals.Value; set => _useCloseSignals.Value = value; }
public decimal OrderVolume { get => _orderVolume.Value; set => _orderVolume.Value = value; }
public decimal TrailingStopSteps { get => _trailingStopSteps.Value; set => _trailingStopSteps.Value = value; }
public decimal TakeProfitSteps { get => _takeProfitSteps.Value; set => _takeProfitSteps.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Ema612Strategy()
{
_shortEmaLength = Param(nameof(ShortEmaLength), 6).SetGreaterThanZero()
.SetDisplay("Short EMA", "Period of the fast EMA", "Indicators");
_longEmaLength = Param(nameof(LongEmaLength), 12).SetGreaterThanZero()
.SetDisplay("Long EMA", "Period of the slow EMA", "Indicators");
_useCloseSignals = Param(nameof(UseCloseSignals), true)
.SetDisplay("Close Signals", "Close on an opposite cross instead of reversing", "Trading");
_orderVolume = Param(nameof(OrderVolume), 1m).SetGreaterThanZero()
.SetDisplay("Order Volume", "Entry size in lots", "Trading");
_trailingStopSteps = Param(nameof(TrailingStopSteps), 0m).SetNotNegative()
.SetDisplay("Trailing Stop Steps", "Zero disables trailing", "Protection");
_takeProfitSteps = Param(nameof(TakeProfitSteps), 0m).SetNotNegative()
.SetDisplay("Take Profit Steps", "Zero disables take profit", "Protection");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Input candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_previousFast = null;
_previousSlow = null;
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previousFast = null;
_previousSlow = null;
_pendingOrder = null;
Volume = NormalizeVolume(OrderVolume);
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
throw new InvalidOperationException("EMA6/12 requires a valid positive PriceStep.");
if (TakeProfitSteps > 0m || TrailingStopSteps > 0m)
StartProtection(
takeProfit: TakeProfitSteps > 0m ? new Unit(TakeProfitSteps * step, UnitTypes.Absolute) : new Unit(),
stopLoss: TrailingStopSteps > 0m ? new Unit(TrailingStopSteps * step, UnitTypes.Absolute) : new Unit(),
isStopTrailing: TrailingStopSteps > 0m,
useMarketOrders: true, isLocalStop: true);
var fast = new ExponentialMovingAverage { Length = ShortEmaLength };
var slow = new ExponentialMovingAverage { Length = LongEmaLength };
SubscribeCandles(CandleType).Bind(fast, slow, ProcessCandle).Start();
}
private decimal NormalizeVolume(decimal requested)
{
if (Security?.VolumeStep is decimal step && step > 0m)
requested = decimal.Floor(requested / step) * step;
if (Security?.MinVolume is decimal min && min > 0m)
requested = Math.Max(requested, min);
if (Security?.MaxVolume is decimal max && max > 0m)
requested = Math.Min(requested, max);
return requested;
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
var previousFast = _previousFast;
var previousSlow = _previousSlow;
_previousFast = fast;
_previousSlow = slow;
if (previousFast is null || previousSlow is null)
return;
var up = previousFast.Value <= previousSlow.Value && fast > slow;
var down = previousFast.Value >= previousSlow.Value && fast < slow;
if (!up && !down || _pendingOrder is not null &&
_pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
var side = up ? Sides.Buy : Sides.Sell;
if (Position == 0m)
{
var volume = NormalizeVolume(OrderVolume);
if (volume > 0m)
Submit(side, volume);
}
else if (UseCloseSignals && (side == Sides.Buy && Position < 0m ||
side == Sides.Sell && Position > 0m))
{
Submit(side, Math.Abs(Position));
}
}
private void Submit(Sides side, decimal volume)
{
_pendingOrder = new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = volume,
};
RegisterOrder(_pendingOrder);
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import Decimal, TimeSpan
from StockSharp.Messages import DataType, CandleStates, OrderTypes, OrderStates, Sides, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from StockSharp.BusinessEntities import Order
class ema612_strategy(Strategy):
"""EMA(6)/EMA(12) cross entries and opposite-cross close-only exits."""
def __init__(self):
super(ema612_strategy, self).__init__()
self._short_ema_length = self.Param("ShortEmaLength", 6).SetGreaterThanZero().SetDisplay(
"Short EMA", "Period of the fast EMA", "Indicators")
self._long_ema_length = self.Param("LongEmaLength", 12).SetGreaterThanZero().SetDisplay(
"Long EMA", "Period of the slow EMA", "Indicators")
self._use_close_signals = self.Param("UseCloseSignals", True).SetDisplay(
"Close Signals", "Close on an opposite cross instead of reversing", "Trading")
self._order_volume = self.Param("OrderVolume", 1.0).SetGreaterThanZero().SetDisplay(
"Order Volume", "Entry size in lots", "Trading")
self._trailing_stop_steps = self.Param("TrailingStopSteps", 0.0).SetNotNegative().SetDisplay(
"Trailing Stop Steps", "Zero disables trailing", "Protection")
self._take_profit_steps = self.Param("TakeProfitSteps", 0.0).SetNotNegative().SetDisplay(
"Take Profit Steps", "Zero disables take profit", "Protection")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay(
"Candle Type", "Input candle timeframe", "General")
self._clear_state()
@property
def short_ema_length(self):
return self._short_ema_length.Value
@property
def long_ema_length(self):
return self._long_ema_length.Value
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def _clear_state(self):
self._previous_fast = None
self._previous_slow = None
self._pending_order = None
def OnReseted(self):
super(ema612_strategy, self).OnReseted()
self._clear_state()
def _normalized_volume(self, volume):
size = float(volume)
step = float(self.Security.VolumeStep or 0)
if step > 0:
size = math.floor(size / step) * step
minimum = float(self.Security.MinVolume or 0)
maximum = float(self.Security.MaxVolume or 0)
if minimum > 0:
size = max(size, minimum)
if maximum > 0:
size = min(size, maximum)
return size
def OnStarted2(self, time):
super(ema612_strategy, self).OnStarted2(time)
self._clear_state()
self.Volume = Decimal(self._normalized_volume(self._order_volume.Value))
step = float(self.Security.PriceStep or 0)
if step <= 0:
raise ValueError("EMA6/12 requires a valid positive PriceStep.")
take = float(self._take_profit_steps.Value)
trail = float(self._trailing_stop_steps.Value)
if take > 0 or trail > 0:
self.StartProtection(
Unit(Decimal(take * step), UnitTypes.Absolute) if take > 0 else Unit(),
Unit(Decimal(trail * step), UnitTypes.Absolute) if trail > 0 else Unit(),
isStopTrailing=trail > 0,
useMarketOrders=True, isLocalStop=True)
fast = ExponentialMovingAverage()
fast.Length = self.short_ema_length
slow = ExponentialMovingAverage()
slow.Length = self.long_ema_length
self.SubscribeCandles(self.candle_type).Bind(fast, slow, self._process_candle).Start()
def _process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
f, s = float(fast), float(slow)
pf, ps = self._previous_fast, self._previous_slow
self._previous_fast, self._previous_slow = f, s
if pf is None or ps is None:
return
up = pf <= ps and f > s
down = pf >= ps and f < s
if not (up or down):
return
if (self._pending_order is not None and
self._pending_order.State not in (OrderStates.Done, OrderStates.Failed)):
return
side = Sides.Buy if up else Sides.Sell
position = float(self.Position)
if position == 0:
volume = self._normalized_volume(self._order_volume.Value)
if volume > 0:
self._submit(side, volume)
elif self._use_close_signals.Value and (
side == Sides.Buy and position < 0 or side == Sides.Sell and position > 0):
self._submit(side, abs(position))
def _submit(self, side, volume):
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = Decimal(volume)
self._pending_order = order
self.RegisterOrder(order)
def CreateClone(self):
return ema612_strategy()