Estrategia de Retroceso de Fibonacci
Esta estrategia opera rupturas de niveles de retroceso de Fibonacci derivados de pivotes ZigZag.
Idea
- Detectar máximos y mínimos de swing mediante un enfoque ZigZag.
- Construir niveles de retroceso de Fibonacci (23.6%, 38.2%, 61.8%, 76.4%) entre los dos últimos pivotes.
- En una tendencia alcista, la estrategia compra cuando el precio cierra por encima de cualquier nivel de Fibonacci.
- En una tendencia bajista, la estrategia vende cuando el precio cierra por debajo de cualquier nivel de Fibonacci.
- Cada orden está protegida con un stop-loss fijo y un take-profit basado en el rango del swing.
- Tras el cierre de una posición, la estrategia espera un número de barras antes de volver a operar.
Parámetros
ZigzagDepth– profundidad utilizada para buscar nuevos pivotes.SafetyBuffer– distancia en puntos que el precio debe moverse más allá del nivel.TrendPrecision– diferencia mínima entre pivotes para detectar la dirección de la tendencia.CloseBarPause– número de barras a esperar tras cerrar una operación.TakeProfitFactor– fracción del rango del swing utilizada como extensión del take-profit.StopLossPoints– distancia del stop-loss desde el precio de entrada en puntos.CandleType– tipo de vela utilizado para los cálculos.
Notas
Este archivo contiene solo la implementación en C#. Aún no se proporciona una versión en Python.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on Fibonacci retracement levels calculated from ZigZag pivots.
/// Buys when price breaks above a Fibonacci level in an uptrend.
/// Sells when price breaks below a Fibonacci level in a downtrend.
/// </summary>
public class FibonacciRetracementStrategy : Strategy
{
private const int BufferSize = 256;
private readonly StrategyParam<int> _zigzagDepth;
private readonly StrategyParam<int> _safetyBuffer;
private readonly StrategyParam<int> _trendPrecision;
private readonly StrategyParam<int> _closeBarPause;
private readonly StrategyParam<decimal> _takeProfitFactor;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<DataType> _candleType;
private readonly decimal[] _highBuffer = new decimal[BufferSize];
private readonly decimal[] _lowBuffer = new decimal[BufferSize];
private bool _longSetupArmed;
private bool _shortSetupArmed;
private decimal _prevClose;
private decimal _entryPrice;
private decimal _stopPrice;
private decimal _takePrice;
private int _barsSinceExit;
private int _bufferIndex;
private int _bufferCount;
/// <summary>
/// Depth parameter for ZigZag pivot detection.
/// </summary>
public int ZigzagDepth
{
get => _zigzagDepth.Value;
set => _zigzagDepth.Value = value;
}
/// <summary>
/// Number of points used as a safety buffer around Fibonacci levels.
/// </summary>
public int SafetyBuffer
{
get => _safetyBuffer.Value;
set => _safetyBuffer.Value = value;
}
/// <summary>
/// Minimal pivot distance in points to determine the trend.
/// </summary>
public int TrendPrecision
{
get => _trendPrecision.Value;
set => _trendPrecision.Value = value;
}
/// <summary>
/// Number of bars to wait after closing a position before trading again.
/// </summary>
public int CloseBarPause
{
get => _closeBarPause.Value;
set => _closeBarPause.Value = value;
}
/// <summary>
/// Take profit multiplier of the last swing range.
/// </summary>
public decimal TakeProfitFactor
{
get => _takeProfitFactor.Value;
set => _takeProfitFactor.Value = value;
}
/// <summary>
/// Stop loss in points from the entry price.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Type of candles used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public FibonacciRetracementStrategy()
{
_zigzagDepth = Param(nameof(ZigzagDepth), 12)
.SetDisplay("ZigZag Depth", "Pivot search depth", "ZigZag");
_safetyBuffer = Param(nameof(SafetyBuffer), 1)
.SetDisplay("Safety Buffer", "Minimum distance from level in points", "General");
_trendPrecision = Param(nameof(TrendPrecision), 5)
.SetDisplay("Trend Precision", "Minimum pivot difference in points", "General");
_closeBarPause = Param(nameof(CloseBarPause), 5)
.SetDisplay("Pause Bars", "Bars to wait after close", "Risk");
_takeProfitFactor = Param(nameof(TakeProfitFactor), 0.2m)
.SetDisplay("Take Profit Factor", "Extension from last extreme", "Risk");
_stopLossPoints = Param(nameof(StopLossPoints), 15)
.SetDisplay("Stop Loss Points", "Distance to stop from entry", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candles timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
Array.Clear(_highBuffer);
Array.Clear(_lowBuffer);
_longSetupArmed = false;
_shortSetupArmed = false;
_prevClose = 0m;
_entryPrice = 0m;
_stopPrice = 0m;
_takePrice = 0m;
_barsSinceExit = CloseBarPause;
_bufferIndex = 0;
_bufferCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Array.Clear(_highBuffer);
Array.Clear(_lowBuffer);
_longSetupArmed = false;
_shortSetupArmed = false;
_prevClose = 0m;
_entryPrice = 0m;
_stopPrice = 0m;
_takePrice = 0m;
_barsSinceExit = CloseBarPause;
_bufferIndex = 0;
_bufferCount = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
PushBar(candle.HighPrice, candle.LowPrice);
_barsSinceExit++;
if (_bufferCount < Math.Min(ZigzagDepth, BufferSize))
{
_prevClose = candle.ClosePrice;
return;
}
var highest = GetHighest(Math.Min(ZigzagDepth, BufferSize));
var lowest = GetLowest(Math.Min(ZigzagDepth, BufferSize));
var range = highest - lowest;
var precision = 0.01m * TrendPrecision;
if (Position > 0)
{
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice)
{
SellMarket();
_entryPrice = 0m;
_longSetupArmed = false;
_barsSinceExit = 0;
}
}
else if (Position < 0)
{
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice)
{
BuyMarket();
_entryPrice = 0m;
_shortSetupArmed = false;
_barsSinceExit = 0;
}
}
if (range <= precision || !IsFormedAndOnlineAndAllowTrading())
{
_prevClose = candle.ClosePrice;
return;
}
var midpoint = lowest + (range / 2m);
var longTrigger = midpoint;
var longRetracement = highest - 0.618m * range;
var shortTrigger = midpoint;
var shortRetracement = lowest + 0.618m * range;
var buffer = 0.01m * SafetyBuffer;
if (Position == 0 && _barsSinceExit >= CloseBarPause)
{
if (candle.ClosePrice > midpoint)
{
_shortSetupArmed = false;
if (candle.LowPrice <= longRetracement + buffer)
_longSetupArmed = true;
if (_longSetupArmed && CrossAbove(_prevClose, candle.ClosePrice, longTrigger, buffer))
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice - 0.01m * StopLossPoints;
_takePrice = highest + TakeProfitFactor * range;
_longSetupArmed = false;
_barsSinceExit = 0;
}
}
else if (candle.ClosePrice < midpoint)
{
_longSetupArmed = false;
if (candle.HighPrice >= shortRetracement - buffer)
_shortSetupArmed = true;
if (_shortSetupArmed && CrossBelow(_prevClose, candle.ClosePrice, shortTrigger, buffer))
{
SellMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice + 0.01m * StopLossPoints;
_takePrice = lowest - TakeProfitFactor * range;
_shortSetupArmed = false;
_barsSinceExit = 0;
}
}
}
_prevClose = candle.ClosePrice;
}
private void PushBar(decimal high, decimal low)
{
_highBuffer[_bufferIndex] = high;
_lowBuffer[_bufferIndex] = low;
_bufferIndex = (_bufferIndex + 1) % BufferSize;
if (_bufferCount < BufferSize)
_bufferCount++;
}
private decimal GetHighest(int depth)
{
var highest = decimal.MinValue;
var count = Math.Min(depth, _bufferCount);
for (var i = 0; i < count; i++)
{
var idx = (_bufferIndex - 1 - i + BufferSize) % BufferSize;
if (_highBuffer[idx] > highest)
highest = _highBuffer[idx];
}
return highest;
}
private decimal GetLowest(int depth)
{
var lowest = decimal.MaxValue;
var count = Math.Min(depth, _bufferCount);
for (var i = 0; i < count; i++)
{
var idx = (_bufferIndex - 1 - i + BufferSize) % BufferSize;
if (_lowBuffer[idx] < lowest)
lowest = _lowBuffer[idx];
}
return lowest;
}
private static bool CrossBelow(decimal prev, decimal current, decimal level, decimal buffer)
{
return prev - level > buffer && level - current > buffer;
}
private static bool CrossAbove(decimal prev, decimal current, decimal level, decimal buffer)
{
return current - level > buffer && level - prev > buffer;
}
}
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.Strategies import Strategy
class fibonacci_retracement_strategy(Strategy):
"""
Strategy based on Fibonacci retracement levels from rolling high/low pivots.
Arms setups when price touches retracement, enters on midpoint cross.
"""
BUFFER_SIZE = 256
def __init__(self):
super(fibonacci_retracement_strategy, self).__init__()
self._zigzag_depth = self.Param("ZigzagDepth", 12) \
.SetDisplay("ZigZag Depth", "Pivot search depth", "ZigZag")
self._safety_buffer = self.Param("SafetyBuffer", 1) \
.SetDisplay("Safety Buffer", "Min distance from level", "General")
self._trend_precision = self.Param("TrendPrecision", 5) \
.SetDisplay("Trend Precision", "Min pivot difference in points", "General")
self._close_bar_pause = self.Param("CloseBarPause", 5) \
.SetDisplay("Pause Bars", "Bars to wait after close", "Risk")
self._take_profit_factor = self.Param("TakeProfitFactor", 0.2) \
.SetDisplay("Take Profit Factor", "Extension from last extreme", "Risk")
self._stop_loss_points = self.Param("StopLossPoints", 15) \
.SetDisplay("Stop Loss Points", "Distance to stop from entry", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candles timeframe", "General")
self._high_buffer = [0.0] * self.BUFFER_SIZE
self._low_buffer = [0.0] * self.BUFFER_SIZE
self._long_setup_armed = False
self._short_setup_armed = False
self._prev_close = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
self._bars_since_exit = 0
self._buffer_index = 0
self._buffer_count = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fibonacci_retracement_strategy, self).OnReseted()
self._high_buffer = [0.0] * self.BUFFER_SIZE
self._low_buffer = [0.0] * self.BUFFER_SIZE
self._long_setup_armed = False
self._short_setup_armed = False
self._prev_close = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
self._bars_since_exit = self._close_bar_pause.Value
self._buffer_index = 0
self._buffer_count = 0
def OnStarted2(self, time):
super(fibonacci_retracement_strategy, self).OnStarted2(time)
self._high_buffer = [0.0] * self.BUFFER_SIZE
self._low_buffer = [0.0] * self.BUFFER_SIZE
self._bars_since_exit = self._close_bar_pause.Value
self._buffer_index = 0
self._buffer_count = 0
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._push_bar(high, low)
self._bars_since_exit += 1
depth = min(self._zigzag_depth.Value, self.BUFFER_SIZE)
if self._buffer_count < depth:
self._prev_close = close
return
highest = self._get_highest(depth)
lowest = self._get_lowest(depth)
rng = highest - lowest
precision = 0.01 * self._trend_precision.Value
if self.Position > 0:
if low <= self._stop_price or high >= self._take_price:
self.SellMarket()
self._entry_price = 0.0
self._long_setup_armed = False
self._bars_since_exit = 0
elif self.Position < 0:
if high >= self._stop_price or low <= self._take_price:
self.BuyMarket()
self._entry_price = 0.0
self._short_setup_armed = False
self._bars_since_exit = 0
if rng <= precision or not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
return
midpoint = lowest + rng / 2.0
long_retracement = highest - 0.618 * rng
short_retracement = lowest + 0.618 * rng
buf = 0.01 * self._safety_buffer.Value
if self.Position == 0 and self._bars_since_exit >= self._close_bar_pause.Value:
if close > midpoint:
self._short_setup_armed = False
if low <= long_retracement + buf:
self._long_setup_armed = True
if self._long_setup_armed and self._cross_above(self._prev_close, close, midpoint, buf):
self.BuyMarket()
self._entry_price = close
self._stop_price = self._entry_price - 0.01 * self._stop_loss_points.Value
self._take_price = highest + self._take_profit_factor.Value * rng
self._long_setup_armed = False
self._bars_since_exit = 0
elif close < midpoint:
self._long_setup_armed = False
if high >= short_retracement - buf:
self._short_setup_armed = True
if self._short_setup_armed and self._cross_below(self._prev_close, close, midpoint, buf):
self.SellMarket()
self._entry_price = close
self._stop_price = self._entry_price + 0.01 * self._stop_loss_points.Value
self._take_price = lowest - self._take_profit_factor.Value * rng
self._short_setup_armed = False
self._bars_since_exit = 0
self._prev_close = close
def _push_bar(self, high, low):
self._high_buffer[self._buffer_index] = high
self._low_buffer[self._buffer_index] = low
self._buffer_index = (self._buffer_index + 1) % self.BUFFER_SIZE
if self._buffer_count < self.BUFFER_SIZE:
self._buffer_count += 1
def _get_highest(self, depth):
highest = -1e18
count = min(depth, self._buffer_count)
for i in range(count):
idx = (self._buffer_index - 1 - i + self.BUFFER_SIZE) % self.BUFFER_SIZE
if self._high_buffer[idx] > highest:
highest = self._high_buffer[idx]
return highest
def _get_lowest(self, depth):
lowest = 1e18
count = min(depth, self._buffer_count)
for i in range(count):
idx = (self._buffer_index - 1 - i + self.BUFFER_SIZE) % self.BUFFER_SIZE
if self._low_buffer[idx] < lowest:
lowest = self._low_buffer[idx]
return lowest
@staticmethod
def _cross_above(prev, current, level, buf):
return current - level > buf and level - prev > buf
@staticmethod
def _cross_below(prev, current, level, buf):
return prev - level > buf and level - current > buf
def CreateClone(self):
return fibonacci_retracement_strategy()