Fibonacci Retracement Strategy
This strategy trades Fibonacci retracement breakouts derived from ZigZag pivots.
Idea
- Detect swing highs and lows using a ZigZag approach.
- Build Fibonacci retracement levels (23.6%, 38.2%, 61.8%, 76.4%) between the last two pivots.
- In an uptrend the strategy buys when price closes above any Fibonacci level.
- In a downtrend the strategy sells when price closes below any Fibonacci level.
- Every order is protected with a fixed stop-loss and a take-profit based on the swing range.
- After a position is closed the strategy waits a number of bars before trading again.
Parameters
ZigzagDepth– depth used to search for new pivots.SafetyBuffer– distance in points that price must move beyond the level.TrendPrecision– minimal difference between pivots to detect trend direction.CloseBarPause– number of bars to wait after closing a trade.TakeProfitFactor– fraction of the swing range used as take-profit extension.StopLossPoints– stop-loss distance from the entry price in points.CandleType– candle type used for calculations.
Notes
This file contains only the C# implementation. A Python version is not yet provided.
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()