Bitcoin 1H/15M Breakout
Handelsregeln
Abgeschlossene 15-Minuten-Kerzen handeln den Ausbruch aus der vorherigen vollständig abgeschlossenen Stunde. Kleine Kerzen werden zu UTC-Stunden zusammengefasst. Nur vier lückenlose Viertel ab exaktem Stundenbeginn ergeben einen gültigen Bereich; ein anfänglicher Teil oder eine Datenlücke liefert keinen.
Für eine kleine Kerze innerhalb Stunde H gelten ausschließlich Hoch/Tief der fertigen Stunde H−1, niemals die laufende Stunde. Ohne Position wird bei Schluss strikt über dem Hoch Volume gekauft bzw. unter dem Tief Volume verkauft. Beide Seiten sind erlaubt; bestehende Positionen werden durch Ausbrüche weder vergrößert noch gedreht. Aktive Orders blockieren neue.
Der native lokale Schutz verwendet StopLossBuffer als absoluten Preisabstand zu tatsächlichen Einstiegsausführungen, standardmäßig 50. Das Gewinnziel beträgt StopLossBuffer * RiskRewardRatio, standardmäßig 100 bei Verhältnis 2. Beide Ausstiege schließen die ganze Position per Marktorder; neue Stundenbereiche verschieben keine Schutzgrenze. HigherTimeFrame muss ein ganzzahliges Vielfaches des kleinen Intervalls sein.
Der native lokale Schutz verwendet Kerzen und abonnierte Preisaktualisierungen des Instruments. Abstände beziehen sich auf tatsächliche Einstiegsausführungen. Ausstiege sind Marktorders; der Ausführungspreis hängt vom verfügbaren Markt und Preisraster ab.
Parameter
| Parameter | Standardwert |
|---|---|
CandleType |
15m |
HigherTimeFrame |
1h |
StopLossBuffer |
50 |
RiskRewardRatio |
2 |
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>Trades lower-timeframe closes outside the preceding complete higher-timeframe range.</summary>
public class Bitcoin1H15MBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<TimeSpan> _higherTimeFrame;
private readonly StrategyParam<decimal> _stopLossBuffer;
private readonly StrategyParam<decimal> _riskRewardRatio;
private DateTime _bucket, _lastTime;
private decimal _high, _low;
private decimal? _previousHigh, _previousLow;
private int _count;
private Order _pending;
/// <summary>Finished lower timeframe candles.</summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>Completed higher timeframe range.</summary>
public TimeSpan HigherTimeFrame
{
get => _higherTimeFrame.Value;
set => _higherTimeFrame.Value = value;
}
/// <summary>Absolute stop distance from actual entry fills.</summary>
public decimal StopLossBuffer
{
get => _stopLossBuffer.Value;
set => _stopLossBuffer.Value = value;
}
/// <summary>Profit distance divided by stop distance.</summary>
public decimal RiskRewardRatio
{
get => _riskRewardRatio.Value;
set => _riskRewardRatio.Value = value;
}
/// <summary>Initializes the fifteen-minute/hourly breakout and fixed protection.</summary>
public Bitcoin1H15MBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("CandleType", "Finished lower timeframe candles", "Parameters");
_higherTimeFrame = Param(nameof(HigherTimeFrame), TimeSpan.FromHours(1))
.SetDisplay("HigherTimeFrame", "Completed higher timeframe range", "Parameters");
_stopLossBuffer = Param(nameof(StopLossBuffer), 50m).SetGreaterThanZero()
.SetDisplay("StopLossBuffer", "Absolute stop distance from actual entry fills", "Parameters");
_riskRewardRatio = Param(nameof(RiskRewardRatio), 2m).SetGreaterThanZero()
.SetDisplay("RiskRewardRatio", "Profit distance divided by stop distance", "Parameters");
OrderRegistering += order => _pending = order;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bucket = _lastTime = default;
_high = _low = 0m;
_previousHigh = _previousLow = null;
_count = 0;
_pending = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
var frame = CandleType.Arg is TimeSpan interval ? interval : TimeSpan.Zero;
if (frame <= TimeSpan.Zero || HigherTimeFrame <= frame || HigherTimeFrame.Ticks % frame.Ticks != 0)
throw new InvalidOperationException("HigherTimeFrame must be an integer multiple of the lower time frame.");
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();
}
var candles = SubscribeCandles(CandleType);
candles.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area is not null)
{
DrawCandles(area, candles);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || candle.OpenTime <= _lastTime)
return;
var frame = (TimeSpan)CandleType.Arg;
var bucket = new DateTime(candle.OpenTime.Ticks / HigherTimeFrame.Ticks * HigherTimeFrame.Ticks, DateTimeKind.Utc);
if (bucket != _bucket)
{
var complete = _bucket != default && bucket == _bucket + HigherTimeFrame
&& _count == HigherTimeFrame.Ticks / frame.Ticks && _lastTime + frame == bucket;
_previousHigh = complete ? _high : null;
_previousLow = complete ? _low : null;
_bucket = bucket;
_high = candle.HighPrice;
_low = candle.LowPrice;
_count = candle.OpenTime == bucket ? 1 : 0;
}
else
{
_count = _count > 0 && candle.OpenTime == _lastTime + frame ? _count + 1 : 0;
_high = Math.Max(_high, candle.HighPrice);
_low = Math.Min(_low, candle.LowPrice);
}
_lastTime = candle.OpenTime;
if (_previousHigh is null || _previousLow is null || Position != 0m || !IsFormedAndOnlineAndAllowTrading()
|| _pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed))
return;
var side = candle.ClosePrice > _previousHigh ? Sides.Buy : candle.ClosePrice < _previousLow ? Sides.Sell : (Sides?)null;
if (side is null) return;
StartProtection(new Unit(StopLossBuffer * RiskRewardRatio, UnitTypes.Absolute),
new Unit(StopLossBuffer, UnitTypes.Absolute), useMarketOrders: true, isLocalStop: true);
if (side == Sides.Buy) BuyMarket(Volume);
else SellMarket(Volume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
import math
from collections import deque
from System import TimeSpan, DateTime, DateTimeKind, Decimal, Nullable
from StockSharp.Messages import DataType, CandleStates, Sides, OrderStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class bitcoin_1h15m_breakout_strategy(Strategy):
def __init__(self):
super(bitcoin_1h15m_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay(
"CandleType", "Finished lower timeframe candles", "Parameters")
self._higher_time_frame = self.Param("HigherTimeFrame", TimeSpan.FromHours(1)).SetDisplay(
"HigherTimeFrame", "Completed higher timeframe range", "Parameters")
self._stop_loss_buffer = self.Param("StopLossBuffer", 50.0).SetGreaterThanZero().SetDisplay(
"StopLossBuffer", "Absolute stop distance from actual entry fills", "Parameters")
self._risk_reward_ratio = self.Param("RiskRewardRatio", 2.0).SetGreaterThanZero().SetDisplay(
"RiskRewardRatio", "Profit distance divided by stop distance", "Parameters")
self._reset_state()
self.OrderRegistering += self._remember_order
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def HigherTimeFrame(self):
return self._higher_time_frame.Value
@HigherTimeFrame.setter
def HigherTimeFrame(self, value):
self._higher_time_frame.Value = value
@property
def StopLossBuffer(self):
return self._stop_loss_buffer.Value
@StopLossBuffer.setter
def StopLossBuffer(self, value):
self._stop_loss_buffer.Value = value
@property
def RiskRewardRatio(self):
return self._risk_reward_ratio.Value
@RiskRewardRatio.setter
def RiskRewardRatio(self, value):
self._risk_reward_ratio.Value = value
def _reset_state(self):
self._bucket = self._last_time = DateTime.MinValue
self._high = self._low = Decimal(0)
self._previous_high = self._previous_low = None
self._count = 0
self._pending = None
def _remember_order(self, order):
self._pending = order
def OnReseted(self):
super(bitcoin_1h15m_breakout_strategy, self).OnReseted()
self._reset_state()
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType), (self.Security, DataType.Level1)]
def OnStarted2(self, time):
frame = self.CandleType.Arg
if not isinstance(frame, TimeSpan) or frame <= TimeSpan.Zero or self.HigherTimeFrame <= frame or self.HigherTimeFrame.Ticks % frame.Ticks != 0:
raise ValueError("HigherTimeFrame must be an integer multiple of the lower time frame.")
super(bitcoin_1h15m_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()
candles = self.SubscribeCandles(self.CandleType)
candles.Bind(self._process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawOwnTrades(area)
def _process(self, candle):
if candle.State != CandleStates.Finished or candle.OpenTime <= self._last_time:
return
frame = self.CandleType.Arg
bucket = DateTime(candle.OpenTime.Ticks // self.HigherTimeFrame.Ticks * self.HigherTimeFrame.Ticks, DateTimeKind.Utc)
if bucket != self._bucket:
complete = self._bucket != DateTime.MinValue and bucket == self._bucket.Add(self.HigherTimeFrame) and self._count == self.HigherTimeFrame.Ticks // frame.Ticks and self._last_time.Add(frame) == bucket
self._previous_high = self._high if complete else None
self._previous_low = self._low if complete else None
self._bucket, self._high, self._low = bucket, candle.HighPrice, candle.LowPrice
self._count = 1 if candle.OpenTime == bucket else 0
else:
self._count = self._count + 1 if self._count > 0 and candle.OpenTime == self._last_time.Add(frame) else 0
self._high = max(self._high, candle.HighPrice)
self._low = min(self._low, candle.LowPrice)
self._last_time = candle.OpenTime
if self._previous_high is None or self._previous_low is None or self.Position != 0 or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed):
return
side = Sides.Buy if candle.ClosePrice > self._previous_high else Sides.Sell if candle.ClosePrice < self._previous_low else None
if side is None:
return
self.StartProtection(Unit(float(self.StopLossBuffer) * float(self.RiskRewardRatio), UnitTypes.Absolute),
Unit(float(self.StopLossBuffer), UnitTypes.Absolute), useMarketOrders=True, isLocalStop=True)
if side == Sides.Buy:
self.BuyMarket(self.Volume)
else:
self.SellMarket(self.Volume)
def CreateClone(self):
return bitcoin_1h15m_breakout_strategy()