Esta estrategia de ruptura coloca órdenes stop al inicio de cada día de trading. Mide el rango diario promedio durante un número configurable de días y utiliza ese valor para derivar los niveles de stop-loss y take-profit. Las órdenes se posicionan en ambos lados del precio actual y solo se espera que uno de ellos se active.
En el OpenHour especificado, la estrategia calcula los precios de buy stop y sell stop a la mitad de la distancia de stop-loss desde el precio de mercado actual. Los niveles de stop-loss y take-profit se definen como porcentajes del rango promedio. Cuando se ejecuta una orden stop, la orden opuesta puede cancelarse o mantenerse para revertir la posición. Una función opcional de martingala multiplica el volumen de la orden restante tras una ejecución.
Las órdenes de entrada pendientes que no se ejecuten antes de CloseHour se eliminan para evitar exposición durante la noche. Tras una entrada, la estrategia coloca inmediatamente órdenes protectoras de stop-loss y take-profit relativas al precio de ejecución.
Detalles
Criterios de entrada:
Calcular el rango diario promedio usando un ATR durante VolatilityDays días.
Calcular las distancias de stop-loss y take-profit como StopLossRate y TakeProfitRate por ciento de ese rango.
En OpenHour colocar órdenes buy y sell stop a offset = stopLoss/2 del precio de mercado.
Criterios de salida:
Las órdenes protectoras de stop-loss y take-profit cierran posiciones.
Las órdenes de entrada pendientes se cancelan en CloseHour.
Modo inverso:
Si Reverse es verdadero, la orden stop opuesta permanece para revertir la posición.
Si UseMartingale también es verdadero, la orden restante se re-registra con el volumen multiplicado por MartingaleMultiplier.
Largo/Corto: Ambas direcciones.
Stops: Stop-loss y take-profit fijos basados en el rango diario.
Valores predeterminados:
VolatilityDays = 5
OpenHour = 7
CloseHour = 10
StopLossRate = 15%
TakeProfitRate = 30%
Reverse = false
UseMartingale = false
MartingaleMultiplier = 2.0
Este enfoque intenta capturar rupturas tras sesiones nocturnas tranquilas, limitando el riesgo mediante objetivos ajustados a la volatilidad.
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>
/// SAW System breakout strategy.
/// Uses ATR to calculate volatility range, then enters on breakout above/below
/// the open price offset by a fraction of ATR.
/// </summary>
public class SawSystem1Strategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _breakoutMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevAtr;
private decimal _sessionOpen;
private bool _traded;
private DateTime _currentDate;
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public decimal BreakoutMultiplier
{
get => _breakoutMultiplier.Value;
set => _breakoutMultiplier.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public SawSystem1Strategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators");
_breakoutMultiplier = Param(nameof(BreakoutMultiplier), 0.5m)
.SetDisplay("Breakout Multiplier", "Fraction of ATR for breakout offset", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevAtr = null;
_sessionOpen = 0;
_traded = false;
_currentDate = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevAtr = null;
_sessionOpen = 0;
_traded = false;
_currentDate = default;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var date = candle.OpenTime.Date;
// New day: record open price and reset
if (date != _currentDate)
{
_currentDate = date;
_sessionOpen = candle.OpenPrice;
_traded = false;
// Close any open position at start of new day
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
_prevAtr = atrValue;
return;
}
if (_traded || _prevAtr is null || _sessionOpen == 0)
{
_prevAtr = atrValue;
return;
}
var offset = _prevAtr.Value * BreakoutMultiplier;
var upperBreak = _sessionOpen + offset;
var lowerBreak = _sessionOpen - offset;
if (candle.ClosePrice > upperBreak && Position <= 0)
{
BuyMarket();
_traded = true;
}
else if (candle.ClosePrice < lowerBreak && Position >= 0)
{
SellMarket();
_traded = true;
}
_prevAtr = atrValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class saw_system1_strategy(Strategy):
def __init__(self):
super(saw_system1_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
self._breakout_multiplier = self.Param("BreakoutMultiplier", 0.5) \
.SetDisplay("Breakout Multiplier", "Fraction of ATR for breakout offset", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_atr = None
self._session_open = 0.0
self._traded = False
self._current_date = None
@property
def atr_period(self):
return self._atr_period.Value
@property
def breakout_multiplier(self):
return self._breakout_multiplier.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(saw_system1_strategy, self).OnReseted()
self._prev_atr = None
self._session_open = 0.0
self._traded = False
self._current_date = None
def OnStarted2(self, time):
super(saw_system1_strategy, self).OnStarted2(time)
self._prev_atr = None
self._session_open = 0.0
self._traded = False
self._current_date = None
atr = AverageTrueRange()
atr.Length = self.atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
atr_value = float(atr_value)
date = candle.OpenTime.Date
if self._current_date is None or date != self._current_date:
self._current_date = date
self._session_open = float(candle.OpenPrice)
self._traded = False
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._prev_atr = atr_value
return
if self._traded or self._prev_atr is None or self._session_open == 0.0:
self._prev_atr = atr_value
return
offset = self._prev_atr * float(self.breakout_multiplier)
upper_break = self._session_open + offset
lower_break = self._session_open - offset
close_price = float(candle.ClosePrice)
if close_price > upper_break and self.Position <= 0:
self.BuyMarket()
self._traded = True
elif close_price < lower_break and self.Position >= 0:
self.SellMarket()
self._traded = True
self._prev_atr = atr_value
def CreateClone(self):
return saw_system1_strategy()