Five Ema NoTouch Breakout
取引規則
EMA 形成後の確定足だけを処理します。足全体が EMA より下なら高値突破の買い、上なら安値割れの売りを準備します。新しい形態で置き換える前に旧形態を判定し、接触は突破としません。空の状態から Volume で入り、保有中は形態を消去します。逆側の極値にストップを固定し、初回実約定時にストップまでのリスク距離と RewardRisk×リスクの利食い距離を固定します。ネイティブのローカル成行保護は足間の Bid/Ask を監視します。約定が既にストップを越えた場合は直ちに決済します。待機足や買い増しはありません。
パラメーター
| パラメーター | 初期値 |
|---|---|
CandleType |
TimeSpan.FromMinutes(5).TimeFrame() |
EmaPeriod |
5 |
RewardRisk |
3 |
検証
両言語を同梱 BTC/TON、独立式、注文方向・数量、実約定、保有、出口で検証します。日付付き価格・評価・暦データは機構検証で、OHLC・出来高の日付移動は複数年の市場履歴ではありません。2区分、TopN=1、SMA=2 は明示的な小規模テスト設定です。長期窓と元の初期値を別に確認し、収益は保証しません。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>Finished no-touch EMA setups with native protection fixed from actual entry risk.</summary>
public class FiveEmaNoTouchBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<decimal> _rewardRisk;
private Order _pending;
private ExponentialMovingAverage _ema;
private (decimal high, decimal low, Sides side)? _setup;
private decimal _setupStop;
private bool _isProtectionArmed;
/// <summary>Finished signal candle type.</summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>EMA length.</summary>
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
/// <summary>Take-profit distance divided by initial risk.</summary>
public decimal RewardRisk { get => _rewardRisk.Value; set => _rewardRisk.Value = value; }
/// <summary>Initialize the strategy parameters.</summary>
public FiveEmaNoTouchBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("CandleType", "Finished signal candle type.", "Parameters");
_emaPeriod = Param(nameof(EmaPeriod), 5).SetRange(1, int.MaxValue)
.SetDisplay("EmaPeriod", "EMA length.", "Parameters");
_rewardRisk = Param(nameof(RewardRisk), 3m).SetRange(0.000001m, decimal.MaxValue)
.SetDisplay("RewardRisk", "Take-profit distance divided by initial risk.", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pending = null;
_ema = null;
_setup = null;
_setupStop = 0m;
_isProtectionArmed = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quote = new Subscription(DataType.Level1, Security);
quote.MarketData.BuildField = field;
SubscribeLevel1(quote).Start();
}
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
SubscribeCandles(CandleType).Bind(_ema, ProcessCandle).Start();
}
private bool IsPending => _pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed);
private void Submit(Sides side, decimal quantity, string comment)
{
_pending = CreateOrder(side, 0m, quantity);
_pending.Type = OrderTypes.Market;
_pending.Comment = comment;
RegisterOrder(_pending);
}
private void ProcessCandle(ICandleMessage candle, decimal ema)
{
if (candle.State != CandleStates.Finished || !_ema.IsFormed || !IsFormedAndOnlineAndAllowTrading() || IsPending) return;
if (Position != 0m)
{
_setup = null;
return;
}
// Test the previous finished setup before a new candle can replace it.
if (_setup is { } setup && ((setup.side == Sides.Buy && candle.HighPrice > setup.high && candle.ClosePrice > setup.low)
|| (setup.side == Sides.Sell && candle.LowPrice < setup.low && candle.ClosePrice < setup.high)))
{
_setupStop = setup.side == Sides.Buy ? setup.low : setup.high;
_isProtectionArmed = false;
_setup = null;
Submit(setup.side, Volume, "0483Entry");
return;
}
if (candle.HighPrice < ema) _setup = (candle.HighPrice, candle.LowPrice, Sides.Buy);
else if (candle.LowPrice > ema) _setup = (candle.HighPrice, candle.LowPrice, Sides.Sell);
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade.Order.Comment != "0483Entry" || _isProtectionArmed) return;
_isProtectionArmed = true;
var risk = trade.Order.Side == Sides.Buy ? trade.Trade.Price - _setupStop : _setupStop - trade.Trade.Price;
if (risk <= 0m)
{
Submit(trade.Order.Side == Sides.Buy ? Sides.Sell : Sides.Buy, Math.Abs(Position), "0483InvalidFillExit");
return;
}
StartProtection(new Unit(risk * RewardRisk, UnitTypes.Absolute), new Unit(risk, UnitTypes.Absolute),
useMarketOrders: true, isLocalStop: true);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import DateTime, Decimal, Math, TimeSpan, InvalidOperationException
from System.Globalization import CultureInfo, DateTimeStyles
from System.IO import File
from StockSharp.Algo.Indicators import ExponentialMovingAverage, SimpleMovingAverage, AverageTrueRange, AlligatorLine, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from StockSharp.BusinessEntities import Subscription
from StockSharp.Messages import Level1Fields, ITickTradeMessage, CandleStates, DataType, OrderStates, OrderTypes, Sides, Unit, UnitTypes
class five_ema_no_touch_breakout_strategy(Strategy):
"""Finished no-touch EMA setups with native protection fixed from actual entry risk."""
def __init__(self):
super(five_ema_no_touch_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("CandleType", "Finished signal candle type.", "Parameters")
self._ema_period = self.Param("EmaPeriod", 5).SetRange(1, 2147483647) \
.SetDisplay("EmaPeriod", "EMA length.", "Parameters")
self._reward_risk = self.Param("RewardRisk", Decimal(3)).SetRange(Decimal(0.000001), Decimal.MaxValue) \
.SetDisplay("RewardRisk", "Take-profit distance divided by initial risk.", "Parameters")
self._clear_state()
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def RewardRisk(self):
return self._reward_risk.Value
@RewardRisk.setter
def RewardRisk(self, value):
self._reward_risk.Value = value
def _clear_state(self):
self._pending = None
self._ema = None
self._setup = None
self._setup_stop = Decimal(0)
self._is_protection_armed = False
def OnReseted(self):
super(five_ema_no_touch_breakout_strategy, self).OnReseted()
self._clear_state()
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnStarted2(self, time):
super(five_ema_no_touch_breakout_strategy, self).OnStarted2(time)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quote = Subscription(DataType.Level1, self.Security)
quote.MarketData.BuildField = field
self.SubscribeLevel1(quote).Start()
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaPeriod
self.SubscribeCandles(self.CandleType).Bind(self._ema, self._process_candle).Start()
def _is_pending(self):
return self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed)
def _submit(self, side, quantity, comment):
self._pending = self.CreateOrder(side, Decimal(0), quantity)
self._pending.Type = OrderTypes.Market
self._pending.Comment = comment
self.RegisterOrder(self._pending)
@staticmethod
def _process(indicator, candle):
value = CandleIndicatorValue(indicator, candle)
value.IsFinal = True
result = indicator.Process(value)
return None if result.IsEmpty else result.GetValue[Decimal](None)
def _process_candle(self, candle, ema):
if candle.State != CandleStates.Finished or not self._ema.IsFormed or not self.IsFormedAndOnlineAndAllowTrading() or self._is_pending():
return
if self.Position != 0:
self._setup = None
return
if self._setup is not None:
high, low, side = self._setup
if (side == Sides.Buy and candle.HighPrice > high and candle.ClosePrice > low) or (side == Sides.Sell and candle.LowPrice < low and candle.ClosePrice < high):
self._setup_stop = low if side == Sides.Buy else high
self._is_protection_armed = False
self._setup = None
self._submit(side, self.Volume, "0483Entry")
return
if candle.HighPrice < ema:
self._setup = (candle.HighPrice, candle.LowPrice, Sides.Buy)
elif candle.LowPrice > ema:
self._setup = (candle.HighPrice, candle.LowPrice, Sides.Sell)
def OnOwnTradeReceived(self, trade):
super(five_ema_no_touch_breakout_strategy, self).OnOwnTradeReceived(trade)
if trade.Order.Comment != "0483Entry" or self._is_protection_armed:
return
self._is_protection_armed = True
risk = ITickTradeMessage.Price.__get__(trade.Trade) - self._setup_stop if trade.Order.Side == Sides.Buy else self._setup_stop - ITickTradeMessage.Price.__get__(trade.Trade)
if risk <= 0:
self._submit(Sides.Sell if trade.Order.Side == Sides.Buy else Sides.Buy, abs(self.Position), "0483InvalidFillExit")
return
self.StartProtection(Unit(risk * self.RewardRisk, UnitTypes.Absolute), Unit(risk, UnitTypes.Absolute), useMarketOrders=True, isLocalStop=True)
def CreateClone(self):
return five_ema_no_touch_breakout_strategy()