斐波那契回撤策略
该策略基于 ZigZag 枢轴点计算斐波那契回撤位并进行突破交易。
思路
- 使用 ZigZag 方法寻找最近的高低点。
- 在最新的两个枢轴点之间构建 23.6%、38.2%、61.8%、76.4% 的回撤位。
- 在上升趋势中,当价格收盘突破任意回撤位时买入。
- 在下降趋势中,当价格收盘跌破任意回撤位时卖出。
- 每笔交易都设置固定止损,并根据波动区间设置止盈。
- 头寸平仓后等待指定数量的K线再进行下一次交易。
参数
ZigzagDepth– 搜索新枢轴点的深度。SafetyBuffer– 价格必须超越回撤位的最小点数。TrendPrecision– 判断趋势方向时枢轴点之间的最小差值。CloseBarPause– 平仓后等待的K线数量。TakeProfitFactor– 根据波动区间计算止盈的比例。StopLossPoints– 入场价到止损的点数距离。CandleType– 用于计算的K线类型。
说明
目前仅提供 C# 实现,尚无 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()