Fibonacci Retracement Reversal Strategy
Markets often retrace a portion of a prior move before resuming trend. This strategy identifies recent swing highs and lows and watches for price to test the 61.8% or 78.6% retracement levels. These areas frequently mark exhaustion points.
Testing indicates an average annual return of about 115%. It performs best in the stocks market.
The algorithm tracks swings over a rolling window and calculates Fibonacci levels between them. When price nears a key retracement and forms a candle in the direction of the original trend, a trade is opened with a stop placed a set percent away. Targets are around the 50% midpoint of the swing.
By focusing on deep pullbacks within an existing trend, the method aims to capture the early stages of a continuation move after sellers or buyers have briefly taken control.
Details
- Entry Criteria: Price tests 61.8% or 78.6% retracement and prints a confirming candle.
- Long/Short: Both depending on trend.
- Exit Criteria: Price reaching the 50% level or stop-loss.
- Stops: Yes, percentage based.
- Default Values:
SwingLookbackPeriod= 20FibLevelBuffer= 0.5 A close within this many percent of a retracement level counts as a test of it.CandleType= 5 minuteStopLossPercent= 2
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Fibonacci levels
- Stops: Yes
- Complexity: Advanced
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Fibonacci Retracement Reversal strategy.
/// The swing is the highest high and lowest low of the previous SwingLookbackPeriod candles; it rose when the low came first.
/// In a rising swing a bullish candle closing within FibLevelBuffer percent of the 61.8% or 78.6% retracement buys,
/// in a falling swing a bearish candle near those levels sells. The target is the swing's 50% level, and a percent stop protects the trade.
/// </summary>
public class FibonacciRetracementReversalStrategy : Strategy
{
private readonly StrategyParam<int> _swingLookbackPeriod;
private readonly StrategyParam<decimal> _fibLevelBuffer;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal High, decimal Low)> _candles = [];
private decimal _target;
/// <summary>
/// Number of previous candles that form the swing.
/// </summary>
public int SwingLookbackPeriod
{
get => _swingLookbackPeriod.Value;
set => _swingLookbackPeriod.Value = value;
}
/// <summary>
/// How close to a retracement level the close must be, in percent of the level.
/// </summary>
public decimal FibLevelBuffer
{
get => _fibLevelBuffer.Value;
set => _fibLevelBuffer.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public FibonacciRetracementReversalStrategy()
{
_swingLookbackPeriod = Param(nameof(SwingLookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Swing Lookback", "Previous candles that form the swing", "Indicators");
_fibLevelBuffer = Param(nameof(FibLevelBuffer), 0.5m)
.SetNotNegative()
.SetDisplay("Level Buffer %", "Distance from a retracement level that counts as a test, in percent", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candles.Clear();
_target = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candles.Clear();
_target = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// The swing is formed by the candles before this one.
var swing = _candles.ToArray();
_candles.Add((candle.HighPrice, candle.LowPrice));
if (_candles.Count > SwingLookbackPeriod)
_candles.RemoveAt(0);
if (swing.Length < SwingLookbackPeriod || !IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (Position > 0)
{
if (close >= _target)
SellMarket(Position);
return;
}
if (Position < 0)
{
if (close <= _target)
BuyMarket(-Position);
return;
}
var highIndex = Array.FindIndex(swing, c => c.High == swing.Max(x => x.High));
var lowIndex = Array.FindIndex(swing, c => c.Low == swing.Min(x => x.Low));
var high = swing[highIndex].High;
var low = swing[lowIndex].Low;
var range = high - low;
if (range <= 0)
return;
var middle = low + range / 2m;
bool Near(decimal level) => Math.Abs(close - level) <= level * FibLevelBuffer / 100m;
if (lowIndex < highIndex)
{
// Rising swing: buy a bullish candle at a deep pullback.
if (close > candle.OpenPrice && close < middle && (Near(high - range * 0.618m) || Near(high - range * 0.786m)))
{
BuyMarket(Volume);
_target = middle;
}
}
else if (highIndex < lowIndex)
{
// Falling swing: sell a bearish candle at a deep rebound.
if (close < candle.OpenPrice && close > middle && (Near(low + range * 0.618m) || Near(low + range * 0.786m)))
{
SellMarket(Volume);
_target = middle;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class fibonacci_retracement_reversal_strategy(Strategy):
"""
Fibonacci Retracement Reversal strategy.
The swing is the highest high and lowest low of the previous SwingLookbackPeriod candles; it rose when the low came first.
In a rising swing a bullish candle closing within FibLevelBuffer percent of the 61.8% or 78.6% retracement buys,
in a falling swing a bearish candle near those levels sells. The target is the swing's 50% level, and a percent stop protects the trade.
"""
def __init__(self):
super(fibonacci_retracement_reversal_strategy, self).__init__()
self._swing_lookback_period = self.Param("SwingLookbackPeriod", 20).SetGreaterThanZero().SetDisplay("Swing Lookback", "Previous candles that form the swing", "Indicators")
self._fib_level_buffer = self.Param("FibLevelBuffer", 0.5).SetNotNegative().SetDisplay("Level Buffer %", "Distance from a retracement level that counts as a test, in percent", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._candles = []
self._target = Decimal(0)
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fibonacci_retracement_reversal_strategy, self).OnReseted()
self._candles = []
self._target = Decimal(0)
def OnStarted2(self, time):
super(fibonacci_retracement_reversal_strategy, self).OnStarted2(time)
self._candles = []
self._target = Decimal(0)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
# The swing is formed by the candles before this one.
period = self._swing_lookback_period.Value
swing = list(self._candles)
self._candles.append((candle.HighPrice, candle.LowPrice))
if len(self._candles) > period:
self._candles.pop(0)
if len(swing) < period or not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
if self.Position > 0:
if close >= self._target:
self.SellMarket(self.Position)
return
if self.Position < 0:
if close <= self._target:
self.BuyMarket(-self.Position)
return
high = max(c[0] for c in swing)
low = min(c[1] for c in swing)
high_index = next(i for i, c in enumerate(swing) if c[0] == high)
low_index = next(i for i, c in enumerate(swing) if c[1] == low)
price_range = high - low
if price_range <= 0:
return
middle = low + price_range / Decimal(2)
buffer = Decimal(self._fib_level_buffer.Value) / Decimal(100)
def near(level):
return Math.Abs(close - level) <= level * buffer
if low_index < high_index:
# Rising swing: buy a bullish candle at a deep pullback.
if close > candle.OpenPrice and close < middle and (near(high - price_range * Decimal(0.618)) or near(high - price_range * Decimal(0.786))):
self.BuyMarket(self.Volume)
self._target = middle
elif high_index < low_index:
# Falling swing: sell a bearish candle at a deep rebound.
if close < candle.OpenPrice and close > middle and (near(low + price_range * Decimal(0.618)) or near(low + price_range * Decimal(0.786))):
self.SellMarket(self.Volume)
self._target = middle
def CreateClone(self):
return fibonacci_retracement_reversal_strategy()