Megabar突破 (Range & Volume & RSI)
Megabar突破寻找具有巨大实体和高成交量的K线,并用RSI均线过滤。看涨megabar做多,看跌megabar做空。
详情
- 入场条件: 蜡烛实体和成交量超过各自均值乘以乘数,RSI均线高于/低于阈值。
- 多空方向: 双向。
- 出场条件: 止损或止盈。
- 止损: 是。
- 默认值:
CandleType= TimeSpan.FromMinutes(5)VolumeAveragePeriod= 20VolumeMultiplier= 3RangeAveragePeriod= 20RangeMultiplier= 4RsiPeriod= 14RsiMaPeriod= 14LongRsiThreshold= 50ShortRsiThreshold= 70TakeProfit= 400StopLoss= 300FilterTradeHours= false
- 过滤器:
- 类别: Breakout
- 方向: Both
- 指标: Volume, Range, RSI
- 止损: Yes
- 复杂度: Basic
- 时间框架: Intraday
- 季节性: No
- 神经网络: No
- 背离: No
- 风险等级: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Megabar Breakout (Range and Volume and RSI) strategy.
/// A megabar has a body larger than RangeMultiplier times the average body over RangeAveragePeriod candles and a volume
/// larger than VolumeMultiplier times the average volume over VolumeAveragePeriod candles. A bullish megabar buys when the
/// RsiMaPeriod moving average of RSI is above LongRsiThreshold, a bearish one sells when it is below ShortRsiThreshold,
/// reversing an opposite position. Exits are a take profit and stop loss in price steps.
/// </summary>
public class MegabarBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _volumeAveragePeriod;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<int> _rangeAveragePeriod;
private readonly StrategyParam<decimal> _rangeMultiplier;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _rsiMaPeriod;
private readonly StrategyParam<decimal> _longRsiThreshold;
private readonly StrategyParam<decimal> _shortRsiThreshold;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<bool> _filterTradeHours;
private SimpleMovingAverage _volumeAverage;
private SimpleMovingAverage _rangeAverage;
private SimpleMovingAverage _rsiAverage;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Candles in the volume average.
/// </summary>
public int VolumeAveragePeriod
{
get => _volumeAveragePeriod.Value;
set => _volumeAveragePeriod.Value = value;
}
/// <summary>
/// Multiple of the average volume a megabar needs.
/// </summary>
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
/// <summary>
/// Candles in the body average.
/// </summary>
public int RangeAveragePeriod
{
get => _rangeAveragePeriod.Value;
set => _rangeAveragePeriod.Value = value;
}
/// <summary>
/// Multiple of the average body a megabar needs.
/// </summary>
public decimal RangeMultiplier
{
get => _rangeMultiplier.Value;
set => _rangeMultiplier.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Period of the RSI moving average.
/// </summary>
public int RsiMaPeriod
{
get => _rsiMaPeriod.Value;
set => _rsiMaPeriod.Value = value;
}
/// <summary>
/// RSI average must be above this level to buy.
/// </summary>
public decimal LongRsiThreshold
{
get => _longRsiThreshold.Value;
set => _longRsiThreshold.Value = value;
}
/// <summary>
/// RSI average must be below this level to sell.
/// </summary>
public decimal ShortRsiThreshold
{
get => _shortRsiThreshold.Value;
set => _shortRsiThreshold.Value = value;
}
/// <summary>
/// Take profit in price steps.
/// </summary>
public int TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Stop loss in price steps.
/// </summary>
public int StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Trade hours filter switch of the original script; the README documents no hours, so it has no effect.
/// </summary>
public bool FilterTradeHours
{
get => _filterTradeHours.Value;
set => _filterTradeHours.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MegabarBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_volumeAveragePeriod = Param(nameof(VolumeAveragePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Average Period", "Candles in the volume average", "Megabar");
_volumeMultiplier = Param(nameof(VolumeMultiplier), 3m)
.SetNotNegative()
.SetDisplay("Volume Multiplier", "Multiple of the average volume", "Megabar");
_rangeAveragePeriod = Param(nameof(RangeAveragePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Range Average Period", "Candles in the body average", "Megabar");
_rangeMultiplier = Param(nameof(RangeMultiplier), 4m)
.SetNotNegative()
.SetDisplay("Range Multiplier", "Multiple of the average body", "Megabar");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "RSI");
_rsiMaPeriod = Param(nameof(RsiMaPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI MA Period", "Period of the RSI moving average", "RSI");
_longRsiThreshold = Param(nameof(LongRsiThreshold), 50m)
.SetDisplay("Long RSI Threshold", "RSI average must be above to buy", "RSI");
_shortRsiThreshold = Param(nameof(ShortRsiThreshold), 70m)
.SetDisplay("Short RSI Threshold", "RSI average must be below to sell", "RSI");
_takeProfit = Param(nameof(TakeProfit), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take profit in price steps", "Risk");
_stopLoss = Param(nameof(StopLoss), 300)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss in price steps", "Risk");
_filterTradeHours = Param(nameof(FilterTradeHours), false)
.SetDisplay("Filter Trade Hours", "Trade hours filter switch of the original script", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_volumeAverage = null;
_rangeAverage = null;
_rsiAverage = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_volumeAverage = new SimpleMovingAverage { Length = VolumeAveragePeriod };
_rangeAverage = new SimpleMovingAverage { Length = RangeAveragePeriod };
_rsiAverage = new SimpleMovingAverage { Length = RsiMaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(rsi, ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 1m;
StartProtection(
TakeProfit > 0 ? new Unit(TakeProfit * step, UnitTypes.Absolute) : new Unit(),
StopLoss > 0 ? new Unit(StopLoss * step, UnitTypes.Absolute) : new Unit(),
useMarketOrders: true, isLocalStop: true);
// The take profit and stop loss have to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private static decimal Feed(SimpleMovingAverage average, decimal value, DateTime time)
=> average.Process(new DecimalIndicatorValue(average, value, time) { IsFinal = true }).GetValue<decimal>();
private void ProcessCandle(ICandleMessage candle, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var averageVolume = Feed(_volumeAverage, candle.TotalVolume, candle.OpenTime);
var averageBody = Feed(_rangeAverage, body, candle.OpenTime);
if (!rsiValue.IsFormed)
return;
var rsiMa = Feed(_rsiAverage, rsiValue.GetValue<decimal>(), candle.OpenTime);
if (!_volumeAverage.IsFormed || !_rangeAverage.IsFormed || !_rsiAverage.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var megabar = body > averageBody * RangeMultiplier && candle.TotalVolume > averageVolume * VolumeMultiplier;
if (!megabar)
return;
if (candle.ClosePrice > candle.OpenPrice && rsiMa > LongRsiThreshold && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (candle.ClosePrice < candle.OpenPrice && rsiMa < ShortRsiThreshold && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
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 TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import RelativeStrengthIndex, SimpleMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class megabar_breakout_strategy(Strategy):
"""
Megabar Breakout (Range and Volume and RSI) strategy.
A megabar has a body larger than RangeMultiplier times the average body over RangeAveragePeriod candles and a volume
larger than VolumeMultiplier times the average volume over VolumeAveragePeriod candles. A bullish megabar buys when the
RsiMaPeriod moving average of RSI is above LongRsiThreshold, a bearish one sells when it is below ShortRsiThreshold,
reversing an opposite position. Exits are a take profit and stop loss in price steps.
"""
def __init__(self):
super(megabar_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_average_period = self.Param("VolumeAveragePeriod", 20).SetGreaterThanZero().SetDisplay("Volume Average Period", "Candles in the volume average", "Megabar")
self._volume_multiplier = self.Param("VolumeMultiplier", 3.0).SetNotNegative().SetDisplay("Volume Multiplier", "Multiple of the average volume", "Megabar")
self._range_average_period = self.Param("RangeAveragePeriod", 20).SetGreaterThanZero().SetDisplay("Range Average Period", "Candles in the body average", "Megabar")
self._range_multiplier = self.Param("RangeMultiplier", 4.0).SetNotNegative().SetDisplay("Range Multiplier", "Multiple of the average body", "Megabar")
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero().SetDisplay("RSI Period", "RSI period", "RSI")
self._rsi_ma_period = self.Param("RsiMaPeriod", 14).SetGreaterThanZero().SetDisplay("RSI MA Period", "Period of the RSI moving average", "RSI")
self._long_rsi_threshold = self.Param("LongRsiThreshold", 50.0).SetDisplay("Long RSI Threshold", "RSI average must be above to buy", "RSI")
self._short_rsi_threshold = self.Param("ShortRsiThreshold", 70.0).SetDisplay("Short RSI Threshold", "RSI average must be below to sell", "RSI")
self._take_profit = self.Param("TakeProfit", 400).SetNotNegative().SetDisplay("Take Profit", "Take profit in price steps", "Risk")
self._stop_loss = self.Param("StopLoss", 300).SetNotNegative().SetDisplay("Stop Loss", "Stop loss in price steps", "Risk")
# Trade hours switch of the original script; the README documents no hours, so it has no effect.
self._filter_trade_hours = self.Param("FilterTradeHours", False).SetDisplay("Filter Trade Hours", "Trade hours filter switch of the original script", "General")
self._volume_average = None
self._range_average = None
self._rsi_average = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(megabar_breakout_strategy, self).OnReseted()
self._volume_average = None
self._range_average = None
self._rsi_average = None
def OnStarted2(self, time):
super(megabar_breakout_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
self._volume_average = SimpleMovingAverage()
self._volume_average.Length = self._volume_average_period.Value
self._range_average = SimpleMovingAverage()
self._range_average.Length = self._range_average_period.Value
self._rsi_average = SimpleMovingAverage()
self._rsi_average.Length = self._rsi_ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(rsi, self._process_candle).Start()
step = self.Security.PriceStep if self.Security is not None and self.Security.PriceStep is not None else Decimal(1)
take_steps = self._take_profit.Value
stop_steps = self._stop_loss.Value
take = Unit(Decimal(take_steps) * step, UnitTypes.Absolute) if take_steps > 0 else Unit()
stop = Unit(Decimal(stop_steps) * step, UnitTypes.Absolute) if stop_steps > 0 else Unit()
self.StartProtection(take, stop, useMarketOrders=True, isLocalStop=True)
# The take profit and stop loss have to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
@staticmethod
def _feed(average, value, time):
indicator_input = DecimalIndicatorValue(average, value, time)
indicator_input.IsFinal = True
return average.Process(indicator_input).GetValue[Decimal](None)
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
body = Math.Abs(candle.ClosePrice - candle.OpenPrice)
average_volume = self._feed(self._volume_average, candle.TotalVolume, candle.OpenTime)
average_body = self._feed(self._range_average, body, candle.OpenTime)
if not rsi_value.IsFormed:
return
rsi_ma = self._feed(self._rsi_average, rsi_value.GetValue[Decimal](None), candle.OpenTime)
if not self._volume_average.IsFormed or not self._range_average.IsFormed or not self._rsi_average.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
megabar = body > average_body * Decimal(self._range_multiplier.Value) and \
candle.TotalVolume > average_volume * Decimal(self._volume_multiplier.Value)
if not megabar:
return
if candle.ClosePrice > candle.OpenPrice and rsi_ma > Decimal(self._long_rsi_threshold.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif candle.ClosePrice < candle.OpenPrice and rsi_ma < Decimal(self._short_rsi_threshold.Value) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return megabar_breakout_strategy()