Bitcoin 1H/15M Breakout
取引ルール
確定した 15 分足で、直前の完全な 1 時間足レンジのブレイクを取引します。小足を UTC 時間枠へ集約し、正確な時間始点から連続する 4 本がすべてある場合だけ有効とします。開始時の部分的な時間枠やデータ欠損ではレンジを作りません。
開始時刻が H 時間内の小足には、完成済み H−1 の高値/安値だけを使い、形成中の H は使いません。建玉がなければ、終値が高値を厳密に上回ると Volume を買い、安値を厳密に下回ると Volume を売ります。両方向を許可し、既存建玉はブレイクで追加・反転しません。未完了注文が次の注文を抑止します。
原生ローカル保護の StopLossBuffer は実際の新規約定からの絶対価格差で、既定 50。利確距離は StopLossBuffer * RiskRewardRatio、既定比率 2 では 100 です。両方とも全建玉の成行決済で、新しい時間レンジによって保護水準は移動しません。HigherTimeFrame は小足間隔の整数倍が必要です。
ネイティブのローカル保護は銘柄の足と購読した価格更新を使用し、距離は実際の新規約定を基準にします。決済は成行で、約定価格は市場の状況と呼値に依存します。
パラメーター
| パラメーター | 既定値 |
|---|---|
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()