Extrapolated Pivot Connector 策略
通过连接最近的枢轴高点和枢轴低点来构建支撑和阻力线。当价格收于阻力线上方时发出做多信号,当价格跌破支撑线时发出做空信号。
细节
- 入场条件:
- 多头: 收盘价上穿阻力线。
- 空头: 收盘价下破支撑线。
- 出场条件:
- 反向信号或仓位反转。
- 指标:
- 基于枢轴点的支撑/阻力线。
- 止损: 无。
- 默认值:
PivotLength= 100HighStart= 1HighEnd= 0LowStart= 1LowEnd= 0
- 过滤器:
- 趋势跟随
- 单一时间框架
- 指标: 枢轴线
- 止损: 无
- 复杂度: 低
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Extrapolated Pivot Connector strategy.
/// Pivot highs and lows are bars that stay the extreme for PivotLength bars on each side. The resistance line connects the pivot
/// high HighStart pivots back with the one HighEnd pivots back (0 is the latest) and is extrapolated to the current bar; the
/// support line does the same with LowStart and LowEnd pivot lows. A close crossing above resistance goes long and a close
/// crossing below support goes short, reversing an opposite position.
/// </summary>
public class ExtrapolatedPivotConnectorStrategy : Strategy
{
private readonly StrategyParam<int> _pivotLength;
private readonly StrategyParam<int> _highStart;
private readonly StrategyParam<int> _highEnd;
private readonly StrategyParam<int> _lowStart;
private readonly StrategyParam<int> _lowEnd;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal high, decimal low)> _window = [];
private readonly List<(int bar, decimal price)> _pivotHighs = [];
private readonly List<(int bar, decimal price)> _pivotLows = [];
private int _barIndex;
private decimal? _prevClose;
private decimal? _prevResistance;
private decimal? _prevSupport;
/// <summary>
/// Bars on each side that a pivot must exceed.
/// </summary>
public int PivotLength
{
get => _pivotLength.Value;
set => _pivotLength.Value = value;
}
/// <summary>
/// Pivot high where the resistance line starts, counted back from the latest (0).
/// </summary>
public int HighStart
{
get => _highStart.Value;
set => _highStart.Value = value;
}
/// <summary>
/// Pivot high where the resistance line ends, counted back from the latest (0).
/// </summary>
public int HighEnd
{
get => _highEnd.Value;
set => _highEnd.Value = value;
}
/// <summary>
/// Pivot low where the support line starts, counted back from the latest (0).
/// </summary>
public int LowStart
{
get => _lowStart.Value;
set => _lowStart.Value = value;
}
/// <summary>
/// Pivot low where the support line ends, counted back from the latest (0).
/// </summary>
public int LowEnd
{
get => _lowEnd.Value;
set => _lowEnd.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ExtrapolatedPivotConnectorStrategy()
{
_pivotLength = Param(nameof(PivotLength), 100)
.SetGreaterThanZero()
.SetDisplay("Pivot Length", "Bars on each side that a pivot must exceed", "Pivots");
_highStart = Param(nameof(HighStart), 1)
.SetNotNegative()
.SetDisplay("High Start", "Pivot high where the resistance line starts", "Pivots");
_highEnd = Param(nameof(HighEnd), 0)
.SetNotNegative()
.SetDisplay("High End", "Pivot high where the resistance line ends", "Pivots");
_lowStart = Param(nameof(LowStart), 1)
.SetNotNegative()
.SetDisplay("Low Start", "Pivot low where the support line starts", "Pivots");
_lowEnd = Param(nameof(LowEnd), 0)
.SetNotNegative()
.SetDisplay("Low End", "Pivot low where the support line ends", "Pivots");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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();
ResetState();
}
private void ResetState()
{
_window.Clear();
_pivotHighs.Clear();
_pivotLows.Clear();
_barIndex = 0;
_prevClose = null;
_prevResistance = null;
_prevSupport = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
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;
var bar = _barIndex++;
UpdatePivots(candle, bar);
var resistance = LineValue(_pivotHighs, HighStart, HighEnd, bar);
var support = LineValue(_pivotLows, LowStart, LowEnd, bar);
var prevClose = _prevClose;
var prevResistance = _prevResistance;
var prevSupport = _prevSupport;
_prevClose = candle.ClosePrice;
_prevResistance = resistance;
_prevSupport = support;
if (prevClose is not decimal lastClose)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var longSignal = resistance is decimal res && prevResistance is decimal lastRes && lastClose <= lastRes && close > res;
var shortSignal = support is decimal sup && prevSupport is decimal lastSup && lastClose >= lastSup && close < sup;
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
private void UpdatePivots(ICandleMessage candle, int bar)
{
var length = PivotLength;
var size = length * 2 + 1;
_window.Add((candle.HighPrice, candle.LowPrice));
if (_window.Count > size)
_window.RemoveAt(0);
if (_window.Count < size)
return;
// The candidate is the middle bar: it is confirmed only after PivotLength newer bars.
var (centerHigh, centerLow) = _window[length];
var isHigh = true;
var isLow = true;
for (var i = 0; i < size && (isHigh || isLow); i++)
{
if (i == length)
continue;
var (high, low) = _window[i];
if (i < length ? high >= centerHigh : high > centerHigh)
isHigh = false;
if (i < length ? low <= centerLow : low < centerLow)
isLow = false;
}
var pivotBar = bar - length;
var keep = Math.Max(Math.Max(HighStart, HighEnd), Math.Max(LowStart, LowEnd)) + 1;
if (isHigh)
AddPivot(_pivotHighs, pivotBar, centerHigh, keep);
if (isLow)
AddPivot(_pivotLows, pivotBar, centerLow, keep);
}
private static void AddPivot(List<(int bar, decimal price)> pivots, int bar, decimal price, int keep)
{
pivots.Insert(0, (bar, price));
if (pivots.Count > keep)
pivots.RemoveAt(pivots.Count - 1);
}
private static decimal? LineValue(List<(int bar, decimal price)> pivots, int start, int end, int bar)
{
if (start >= pivots.Count || end >= pivots.Count)
return null;
var (startBar, startPrice) = pivots[start];
var (endBar, endPrice) = pivots[end];
if (startBar == endBar)
return null;
var slope = (endPrice - startPrice) / (endBar - startBar);
return endPrice + slope * (bar - endBar);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class extrapolated_pivot_connector_strategy(Strategy):
"""
Extrapolated Pivot Connector strategy.
Pivot highs and lows are bars that stay the extreme for PivotLength bars on each side. The resistance line connects the pivot
high HighStart pivots back with the one HighEnd pivots back (0 is the latest) and is extrapolated to the current bar; the
support line does the same with LowStart and LowEnd pivot lows. A close crossing above resistance goes long and a close
crossing below support goes short, reversing an opposite position.
"""
def __init__(self):
super(extrapolated_pivot_connector_strategy, self).__init__()
self._pivot_length = self.Param("PivotLength", 100).SetGreaterThanZero().SetDisplay("Pivot Length", "Bars on each side that a pivot must exceed", "Pivots")
self._high_start = self.Param("HighStart", 1).SetNotNegative().SetDisplay("High Start", "Pivot high where the resistance line starts", "Pivots")
self._high_end = self.Param("HighEnd", 0).SetNotNegative().SetDisplay("High End", "Pivot high where the resistance line ends", "Pivots")
self._low_start = self.Param("LowStart", 1).SetNotNegative().SetDisplay("Low Start", "Pivot low where the support line starts", "Pivots")
self._low_end = self.Param("LowEnd", 0).SetNotNegative().SetDisplay("Low End", "Pivot low where the support line ends", "Pivots")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._highs = []
self._lows = []
self._pivot_highs = []
self._pivot_lows = []
self._bar_index = 0
self._prev_close = None
self._prev_resistance = None
self._prev_support = None
def OnReseted(self):
super(extrapolated_pivot_connector_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(extrapolated_pivot_connector_strategy, self).OnStarted2(time)
self._reset_state()
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
bar = self._bar_index
self._bar_index += 1
self._update_pivots(candle, bar)
resistance = self._line_value(self._pivot_highs, self._high_start.Value, self._high_end.Value, bar)
support = self._line_value(self._pivot_lows, self._low_start.Value, self._low_end.Value, bar)
close = float(candle.ClosePrice)
last_close = self._prev_close
last_res = self._prev_resistance
last_sup = self._prev_support
self._prev_close = close
self._prev_resistance = resistance
self._prev_support = support
if last_close is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
long_signal = resistance is not None and last_res is not None and last_close <= last_res and close > resistance
short_signal = support is not None and last_sup is not None and last_close >= last_sup and close < support
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def _update_pivots(self, candle, bar):
length = self._pivot_length.Value
size = length * 2 + 1
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
if len(self._highs) > size:
del self._highs[0]
del self._lows[0]
if len(self._highs) < size:
return
# The candidate is the middle bar: it is confirmed only after PivotLength newer bars.
center_high = self._highs[length]
center_low = self._lows[length]
is_high = max(self._highs[:length]) < center_high and max(self._highs[length + 1:]) <= center_high
is_low = min(self._lows[:length]) > center_low and min(self._lows[length + 1:]) >= center_low
pivot_bar = bar - length
keep = max(self._high_start.Value, self._high_end.Value, self._low_start.Value, self._low_end.Value) + 1
if is_high:
self._add_pivot(self._pivot_highs, pivot_bar, center_high, keep)
if is_low:
self._add_pivot(self._pivot_lows, pivot_bar, center_low, keep)
def _add_pivot(self, pivots, bar, price, keep):
pivots.insert(0, (bar, price))
if len(pivots) > keep:
pivots.pop()
def _line_value(self, pivots, start, end, bar):
if start >= len(pivots) or end >= len(pivots):
return None
start_bar, start_price = pivots[start]
end_bar, end_price = pivots[end]
if start_bar == end_bar:
return None
slope = (end_price - start_price) / (end_bar - start_bar)
return end_price + slope * (bar - end_bar)
def CreateClone(self):
return extrapolated_pivot_connector_strategy()