Five Ema NoTouch Breakout
交易规则
EMA 形成后仅处理完整蜡烛。蜡烛完全低于 EMA 时准备突破其高点做多,完全高于时准备跌破其低点做空。先检查旧形态再替换,触及不算突破。只从空仓以 Volume 入场,持仓时清除形态。止损固定于形态的另一端极值;首次真实成交时固定到止损的风险距离及 RewardRisk×风险的止盈距离。本地原生市价保护在蜡烛间监听买卖报价。若成交已越过止损则立即平仓。无冷却期或加仓。
参数
| 参数 | 默认值 |
|---|---|
CandleType |
TimeSpan.FromMinutes(5).TimeFrame() |
EmaPeriod |
5 |
RewardRisk |
3 |
验证
两种语言使用随附 BTC/TON,检查独立公式、订单方向/数量、真实成交、仓位与退出。带日期的价格、因子、日历数据验证机制,重定位 OHLC/成交量不是多年历史。两个排名组、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()