Bitcoin 1H/15M Breakout
Reglas de negociación
Opera rupturas en velas terminadas de 15 minutos del rango de la hora anterior completa. Las velas menores se agregan en horas UTC. Una hora solo es válida si contiene los cuatro cuartos consecutivos desde su inicio exacto; una hora inicial parcial o un hueco no crea rango.
Para una vela menor abierta en la hora H, usar exclusivamente el máximo/mínimo de H−1 ya completa, nunca la hora actual parcial. Sin posición, comprar Volume con cierre estrictamente por encima del máximo o vender Volume por debajo del mínimo. Se permiten ambas direcciones; una posición abierta no aumenta ni revierte por nuevas rupturas. Una orden activa bloquea otra.
La protección local nativa fija StopLossBuffer como distancia absoluta desde ejecuciones de entrada, predeterminado 50. El take-profit usa StopLossBuffer * RiskRewardRatio, predeterminado 100 con razón 2. Ambas salidas son a mercado por toda la posición y no cambian con nuevos rangos horarios. HigherTimeFrame debe ser múltiplo entero del intervalo menor.
La protección local nativa usa velas y actualizaciones de precio suscritas del instrumento. Las distancias parten de los precios ejecutados. Las salidas son a mercado y dependen del mercado disponible y del paso de precio.
Parámetros
| Parámetro | Predeterminado |
|---|---|
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()