Estrategia de Ruptura Diaria
Esta estrategia opera rupturas desde la apertura diaria. Al comienzo de cada nuevo día se almacena el precio de apertura. Cuando el precio se aleja de este nivel por un número de puntos definido por el usuario, y la barra anterior está dentro de un rango de tamaño configurable, la estrategia entra en la dirección de la ruptura.
Lógica de entrada
- Si la barra anterior es alcista y el precio sube por encima de la apertura diaria en Break Point puntos, se abre una posición larga.
- Si la barra anterior es bajista y el precio cae por debajo de la apertura diaria en Break Point puntos, se abre una posición corta.
- El tamaño de la barra anterior debe estar entre Last Bar Min y Last Bar Max puntos.
- El nivel de ruptura debe estar dentro del cuerpo de la barra anterior.
Gestión del riesgo
- El Take Profit y Stop Loss opcionales se miden en puntos desde el precio de entrada.
- Se puede activar un trailing stop con los parámetros Trailing Start, Trailing Stop y Trailing Step. Cuando el precio se mueve a favor en Trailing Start puntos, el stop se establece en Trailing Stop puntos desde la entrada y luego sigue con incrementos de Trailing Step.
Parámetros
| Nombre | Descripción |
|---|---|
| Candle Type | Marco temporal de las velas procesadas. |
| Break Point | Distancia desde la apertura diaria para activar una operación (puntos). |
| Last Bar Min | Tamaño mínimo de la barra anterior (puntos). |
| Last Bar Max | Tamaño máximo de la barra anterior (puntos). |
| Trailing Start | Movimiento de precio para iniciar el trailing stop (puntos). |
| Trailing Stop | Distancia inicial del trailing stop (puntos). |
| Trailing Step | Paso para mover el trailing stop (puntos). |
| Take Profit | Distancia de take profit (puntos). |
| Stop Loss | Distancia de stop loss (puntos). |
Notas
La estrategia opera solo en velas terminadas y utiliza órdenes de mercado para entradas y salidas. Almacena variables internas para los datos de la barra anterior y el nivel del trailing stop.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Daily-open breakout in security price steps, gated by the preceding
/// completed candle's body. Fixed protection uses actual fills and the optional
/// trailing exit is evaluated only on subsequent completed candles.
/// </summary>
public class DailyBreakpointStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _breakPoint, _lastBarMin, _lastBarMax;
private readonly StrategyParam<decimal> _takeProfit, _stopLoss;
private readonly StrategyParam<decimal> _trailingStart, _trailingStop, _trailingStep;
private decimal _dailyOpen, _previousOpen, _previousClose;
private DateTimeOffset _previousTime;
private bool _hasPrevious;
private decimal? _entryPrice, _bestPrice, _trailPrice;
private decimal _trackedPosition;
private Order _pending;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal BreakPoint { get => _breakPoint.Value; set => _breakPoint.Value = value; }
public decimal LastBarMin { get => _lastBarMin.Value; set => _lastBarMin.Value = value; }
public decimal LastBarMax { get => _lastBarMax.Value; set => _lastBarMax.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TrailingStart { get => _trailingStart.Value; set => _trailingStart.Value = value; }
public decimal TrailingStop { get => _trailingStop.Value; set => _trailingStop.Value = value; }
public decimal TrailingStep { get => _trailingStep.Value; set => _trailingStep.Value = value; }
public DailyBreakpointStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Signal timeframe", "General");
_breakPoint = Param(nameof(BreakPoint), 30m).SetNotNegative()
.SetDisplay("Break Point", "Daily-open breakout offset in price steps", "Trading");
_lastBarMin = Param(nameof(LastBarMin), 5m).SetNotNegative()
.SetDisplay("Last Bar Min", "Minimum previous candle body in price steps", "Trading");
_lastBarMax = Param(nameof(LastBarMax), 100m).SetNotNegative()
.SetDisplay("Last Bar Max", "Maximum previous candle body in price steps", "Trading");
_takeProfit = Param(nameof(TakeProfit), 200m).SetNotNegative()
.SetDisplay("Take Profit", "Optional target in price steps", "Risk");
_stopLoss = Param(nameof(StopLoss), 100m).SetNotNegative()
.SetDisplay("Stop Loss", "Optional risk in price steps", "Risk");
_trailingStart = Param(nameof(TrailingStart), 0m).SetNotNegative()
.SetDisplay("Trailing Start", "Favorable move activating trailing in steps", "Risk");
_trailingStop = Param(nameof(TrailingStop), 0m).SetNotNegative()
.SetDisplay("Trailing Stop", "Trail distance; zero disables trailing", "Risk");
_trailingStep = Param(nameof(TrailingStep), 1m).SetGreaterThanZero()
.SetDisplay("Trailing Step", "Minimum improvement before a trail update", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
private void ResetState()
{
_dailyOpen = _previousOpen = _previousClose = 0m;
_previousTime = default;
_hasPrevious = false;
_entryPrice = _bestPrice = _trailPrice = null;
_trackedPosition = 0m;
_pending = null;
}
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var step = Security.PriceStep is decimal valid && valid > 0m ? valid : 1m;
StartProtection(
takeProfit: new Unit(TakeProfit * step, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss * step, UnitTypes.Absolute),
useMarketOrders: true);
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
}
protected override void OnNewPosition(Position position)
{
base.OnNewPosition(position);
ObservePosition(position);
}
protected override void OnPositionChanged(Position position)
{
base.OnPositionChanged(position);
ObservePosition(position);
}
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
ObservePosition(position);
}
private void ObservePosition(Position position)
{
if (position.Security != Security || position.Portfolio != Portfolio) return;
var volume = position.CurrentValue ?? 0m;
if (volume == 0m)
{
_entryPrice = _bestPrice = _trailPrice = null;
_trackedPosition = 0m;
return;
}
if (position.AveragePrice is not decimal avg || avg <= 0m) return;
if (_entryPrice == avg && _trackedPosition * volume > 0m)
{
_trackedPosition = volume;
return;
}
_entryPrice = _bestPrice = avg;
_trailPrice = null;
_trackedPosition = volume;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished) return;
// The daily open is pinned to the very first completed candle of that day.
var isNewDay = !_hasPrevious || candle.OpenTime.Date != _previousTime.Date;
if (isNewDay) _dailyOpen = candle.OpenPrice;
var step = Security.PriceStep is decimal valid && valid > 0m ? valid : 1m;
var price = candle.ClosePrice;
if (IsFormedAndOnlineAndAllowTrading() &&
(_pending is null || _pending.State is OrderStates.Done or OrderStates.Failed))
{
if (Position != 0m && _entryPrice is decimal entry && TrailingStop > 0m)
{
var isLong = Position > 0m;
var best = _bestPrice ?? entry;
best = isLong ? Math.Max(best, price) : Math.Min(best, price);
_bestPrice = best;
if ((isLong ? best - entry : entry - best) >= TrailingStart * step)
{
var candidate = isLong ? best - TrailingStop * step : best + TrailingStop * step;
if (_trailPrice is not decimal current ||
(isLong ? candidate >= current + TrailingStep * step
: candidate <= current - TrailingStep * step))
_trailPrice = candidate;
}
if (_trailPrice is decimal trail && (isLong ? price <= trail : price >= trail))
_pending = isLong ? SellMarket(Math.Abs(Position)) : BuyMarket(Math.Abs(Position));
}
else if (Position == 0m && !isNewDay && _hasPrevious && _previousTime.Date == candle.OpenTime.Date)
{
var range = Math.Abs(_previousClose - _previousOpen);
var offset = BreakPoint * step;
var buyBreak = _dailyOpen + offset;
var sellBreak = _dailyOpen - offset;
var validSize = range >= LastBarMin * step && range <= LastBarMax * step;
if (validSize && _previousClose > _previousOpen && price > buyBreak &&
buyBreak >= _previousOpen && buyBreak <= _previousClose)
_pending = BuyMarket(Volume);
else if (validSize && _previousClose < _previousOpen && price < sellBreak &&
sellBreak <= _previousOpen && sellBreak >= _previousClose)
_pending = SellMarket(Volume);
}
}
_previousOpen = candle.OpenPrice;
_previousClose = candle.ClosePrice;
_previousTime = candle.OpenTime;
_hasPrevious = true;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, OrderStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class daily_breakpoint_strategy(Strategy):
"""Daily-open breakout with previous candle body and price-step protections."""
def __init__(self):
super(daily_breakpoint_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay(
"Candle Type", "Signal timeframe", "General")
self._break_point = self.Param("BreakPoint", 30.0).SetNotNegative().SetDisplay(
"Break Point", "Daily-open breakout offset in price steps", "Trading")
self._last_bar_min = self.Param("LastBarMin", 5.0).SetNotNegative().SetDisplay(
"Last Bar Min", "Minimum previous candle body in steps", "Trading")
self._last_bar_max = self.Param("LastBarMax", 100.0).SetNotNegative().SetDisplay(
"Last Bar Max", "Maximum previous candle body in steps", "Trading")
self._take_profit = self.Param("TakeProfit", 200.0).SetNotNegative().SetDisplay(
"Take Profit", "Optional target in price steps", "Risk")
self._stop_loss = self.Param("StopLoss", 100.0).SetNotNegative().SetDisplay(
"Stop Loss", "Optional risk in price steps", "Risk")
self._trailing_start = self.Param("TrailingStart", 0.0).SetNotNegative().SetDisplay(
"Trailing Start", "Activation gain in price steps", "Risk")
self._trailing_stop = self.Param("TrailingStop", 0.0).SetNotNegative().SetDisplay(
"Trailing Stop", "Trail distance; zero disables trailing", "Risk")
self._trailing_step = self.Param("TrailingStep", 1.0).SetGreaterThanZero().SetDisplay(
"Trailing Step", "Minimum improvement to adjust trailing", "Risk")
self._reset_state()
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _reset_state(self):
self._daily_open = self._previous_open = self._previous_close = 0.0
self._previous_time = None
self._has_previous = False
self._entry_price = self._best_price = self._trail_price = None
self._tracked_position = 0.0
self._pending = None
def OnReseted(self):
super(daily_breakpoint_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(daily_breakpoint_strategy, self).OnStarted2(time)
self._reset_state()
step = float(self.Security.PriceStep or 1)
if step <= 0:
step = 1
self.StartProtection(
takeProfit=Unit(float(self._take_profit.Value) * step, UnitTypes.Absolute),
stopLoss=Unit(float(self._stop_loss.Value) * step, UnitTypes.Absolute),
useMarketOrders=True)
self.SubscribeCandles(self.CandleType).Bind(self.ProcessCandle).Start()
def OnNewPosition(self, position):
super(daily_breakpoint_strategy, self).OnNewPosition(position)
self._observe_position(position)
def OnPositionChanged(self, position):
super(daily_breakpoint_strategy, self).OnPositionChanged(position)
self._observe_position(position)
def OnPositionReceived(self, position):
super(daily_breakpoint_strategy, self).OnPositionReceived(position)
self._observe_position(position)
def _observe_position(self, position):
if position.Security != self.Security or position.Portfolio != self.Portfolio:
return
volume = float(position.CurrentValue or 0)
if volume == 0:
self._entry_price = self._best_price = self._trail_price = None
self._tracked_position = 0.0
return
if position.AveragePrice is None or position.AveragePrice <= 0:
return
avg = float(position.AveragePrice)
if self._entry_price == avg and self._tracked_position * volume > 0:
self._tracked_position = volume
return
self._entry_price = self._best_price = avg
self._trail_price = None
self._tracked_position = volume
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
new_day = not self._has_previous or candle.OpenTime.Date != self._previous_time.Date
if new_day:
self._daily_open = float(candle.OpenPrice)
price = float(candle.ClosePrice)
step = float(self.Security.PriceStep or 1)
if step <= 0:
step = 1
can_order = self.IsFormedAndOnlineAndAllowTrading() and (
self._pending is None or self._pending.State in (OrderStates.Done, OrderStates.Failed))
if can_order:
pos = float(self.Position)
if pos != 0 and self._entry_price is not None and float(self._trailing_stop.Value) > 0:
entry = self._entry_price
is_long = pos > 0
best = self._best_price if self._best_price is not None else entry
best = max(best, price) if is_long else min(best, price)
self._best_price = best
if (best - entry if is_long else entry - best) >= float(self._trailing_start.Value) * step:
candidate = best - float(self._trailing_stop.Value) * step if is_long else best + float(self._trailing_stop.Value) * step
current = self._trail_price
movement = float(self._trailing_step.Value) * step
if current is None or (candidate >= current + movement if is_long else candidate <= current - movement):
self._trail_price = candidate
if self._trail_price is not None and (price <= self._trail_price if is_long else price >= self._trail_price):
self._pending = self.SellMarket(abs(self.Position)) if is_long else self.BuyMarket(abs(self.Position))
elif pos == 0 and not new_day and self._has_previous and self._previous_time.Date == candle.OpenTime.Date:
body = abs(self._previous_close - self._previous_open)
offset = float(self._break_point.Value) * step
buy_level = self._daily_open + offset
sell_level = self._daily_open - offset
valid_size = float(self._last_bar_min.Value) * step <= body <= float(self._last_bar_max.Value) * step
if valid_size and self._previous_close > self._previous_open and price > buy_level and self._previous_open <= buy_level <= self._previous_close:
self._pending = self.BuyMarket(self.Volume)
elif valid_size and self._previous_close < self._previous_open and price < sell_level and self._previous_close <= sell_level <= self._previous_open:
self._pending = self.SellMarket(self.Volume)
self._previous_open = float(candle.OpenPrice)
self._previous_close = price
self._previous_time = candle.OpenTime
self._has_previous = True
def CreateClone(self):
return daily_breakpoint_strategy()