上一根蜡烛突破策略
本策略移植自 Soubra2003 的 MetaTrader 专家顾问 BreakOut。算法保存上一根已完成蜡烛的最高价与最低价,只要当 前收盘价突破这些参照水平,就会立即平仓并按突破方向开仓。用户还可以以价格单位设置可选的止损与止盈距离, 以控制风险或锁定利润。
策略概览
- 订阅单一的蜡烛序列(默认使用 1 小时周期)。
- 每根蜡烛收盘后记录其高点与低点,作为下一根蜡烛的突破触发值。
- 所有交易均在蜡烛收盘时执行,从而在没有逐笔数据的情况下重现原始 MQL4 逻辑。
交易规则
- 突破入场与反转
- 如果当前收盘价高于上一根蜡烛的最高价:
- 如有空头持仓则先以市价买入平仓。
- 随后立即开立新的多头头寸(反转在同一次蜡烛处理过程中完成)。
- 如果当前收盘价低于上一根蜡烛的最低价:
- 如有多头持仓则先以市价卖出平仓。
- 随后开立新的空头头寸。
- 如果当前收盘价高于上一根蜡烛的最高价:
- 保护性离场(可选)
- 当止损距离大于 0 时,多头在收盘价低于入场价减去该距离时平仓;空头在收盘价高于入场价加上该距离时平仓。
- 当止盈距离大于 0 时,多头在收盘价高于入场价加上该距离时平仓;空头在收盘价低于入场价减去该距离时平仓。
- 水平更新
- 每次处理完成后,当前蜡烛的高低价会成为下一根蜡烛的突破参照。
参数说明
- Candle Type – 蜡烛数据类型(默认为 1 小时)。请设置为与原始 MetaTrader 图表相同的周期。
- Stop Loss – 入场价与止损之间的绝对价格距离,设置为
0表示不启用止损。 - Take Profit – 入场价与止盈之间的绝对价格距离,设置为
0表示不启用止盈。
备注
- 原版在下单时直接设置 SL/TP。StockSharp 版本改为在收盘价触发条件时发送市价单离场。
- 配置止损/止盈时请结合品种的最小报价单位。例如最小变动价位为 0.01 且希望使用 20 个跳动点,则参数应设置为
0.20。 - 由于策略仅参考最近一根蜡烛的区间,在趋势行情或高波动时表现最佳。
来源
- 文件:
MQL/17306/BreakOut.mq4 - 作者: https://www.mql5.com/en/users/soubra2003
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>
/// Breakout strategy that trades when the close crosses the previous candle's high or low.
/// Ported from the BreakOut.mq4 expert by Soubra2003.
/// </summary>
public class PreviousCandleBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossOffset;
private readonly StrategyParam<decimal> _takeProfitOffset;
private decimal? _previousHigh;
private decimal? _previousLow;
private decimal _entryPrice;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossOffset { get => _stopLossOffset.Value; set => _stopLossOffset.Value = value; }
public decimal TakeProfitOffset { get => _takeProfitOffset.Value; set => _takeProfitOffset.Value = value; }
public PreviousCandleBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candle subscription", "General");
_stopLossOffset = Param(nameof(StopLossOffset), 1000m)
.SetDisplay("Stop Loss", "Price distance for the stop-loss. Set 0 to disable.", "Risk")
;
_takeProfitOffset = Param(nameof(TakeProfitOffset), 1500m)
.SetDisplay("Take Profit", "Price distance for the take-profit. Set 0 to disable.", "Risk")
;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousHigh = null;
_previousLow = null;
_entryPrice = 0m;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_previousHigh is null || _previousLow is null)
{
// Store the first finished candle to obtain reference high/low levels.
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
return;
}
var previousHigh = _previousHigh.Value;
var previousLow = _previousLow.Value;
var close = candle.ClosePrice;
var breakoutAbove = close > previousHigh;
var breakoutBelow = close < previousLow;
// Manage protective exits while a position is open.
if (Position > 0)
{
if (StopLossOffset > 0m && close <= _entryPrice - StopLossOffset)
{
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
_entryPrice = 0m;
}
else if (TakeProfitOffset > 0m && close >= _entryPrice + TakeProfitOffset)
{
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
_entryPrice = 0m;
}
}
else if (Position < 0)
{
if (StopLossOffset > 0m && close >= _entryPrice + StopLossOffset)
{
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
_entryPrice = 0m;
}
else if (TakeProfitOffset > 0m && close <= _entryPrice - TakeProfitOffset)
{
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
_entryPrice = 0m;
}
}
// Breakout above the previous high opens or reverses into a long position.
if (breakoutAbove)
{
if (Position < 0)
{
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
_entryPrice = 0m;
}
if (Position <= 0)
{
BuyMarket();
_entryPrice = close;
}
}
else if (breakoutBelow)
{
// Breakout below the previous low opens or reverses into a short position.
if (Position > 0)
{
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
_entryPrice = 0m;
}
if (Position >= 0)
{
SellMarket();
_entryPrice = close;
}
}
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class previous_candle_breakout_strategy(Strategy):
def __init__(self):
super(previous_candle_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromDays(1)))
self._stop_loss_offset = self.Param("StopLossOffset", 1000.0)
self._take_profit_offset = self.Param("TakeProfitOffset", 1500.0)
self._previous_high = None
self._previous_low = None
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StopLossOffset(self):
return self._stop_loss_offset.Value
@StopLossOffset.setter
def StopLossOffset(self, value):
self._stop_loss_offset.Value = value
@property
def TakeProfitOffset(self):
return self._take_profit_offset.Value
@TakeProfitOffset.setter
def TakeProfitOffset(self, value):
self._take_profit_offset.Value = value
def OnStarted2(self, time):
super(previous_candle_breakout_strategy, self).OnStarted2(time)
self._previous_high = None
self._previous_low = None
self._entry_price = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._previous_high is None or self._previous_low is None:
self._previous_high = high
self._previous_low = low
return
previous_high = self._previous_high
previous_low = self._previous_low
breakout_above = close > previous_high
breakout_below = close < previous_low
sl = float(self.StopLossOffset)
tp = float(self.TakeProfitOffset)
if self.Position > 0:
if sl > 0.0 and close <= self._entry_price - sl:
self.SellMarket()
self._entry_price = 0.0
elif tp > 0.0 and close >= self._entry_price + tp:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if sl > 0.0 and close >= self._entry_price + sl:
self.BuyMarket()
self._entry_price = 0.0
elif tp > 0.0 and close <= self._entry_price - tp:
self.BuyMarket()
self._entry_price = 0.0
if breakout_above:
if self.Position < 0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position <= 0:
self.BuyMarket()
self._entry_price = close
elif breakout_below:
if self.Position > 0:
self.SellMarket()
self._entry_price = 0.0
if self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._previous_high = high
self._previous_low = low
def OnReseted(self):
super(previous_candle_breakout_strategy, self).OnReseted()
self._previous_high = None
self._previous_low = None
self._entry_price = 0.0
def CreateClone(self):
return previous_candle_breakout_strategy()