Advertisement
niquedegraaff

ZigZag

Feb 23rd, 2023
1,156
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.20 KB | Cryptocurrency | 0 0
  1. // This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
  2. // © TradingView
  3.  
  4. //@version=5
  5. library("ZigZag", overlay = true)
  6.  
  7. // ZigZag Library
  8. // v3, 2022.12.20
  9.  
  10. // This code was written using the recommendations from the Pine Script™ User Manual's Style Guide:
  11. //   https://www.tradingview.com/pine-script-docs/en/v5/writing/Style_guide.html
  12.  
  13.  
  14.  
  15. //#region ———————————————————— Inputs
  16.  
  17.  
  18. float  deviationInput = input.float(5.0,         "Deviation (%)",                       minval = 0.00001, maxval = 100.0)
  19. int    depthInput     = input.int(10,            "Depth",                               minval = 1)
  20. color  lineColorInput = input.color(#2962FF,   "Line Color")
  21. bool   extendInput    = input.bool(true,         "Extend to Last Bar")
  22. bool   showPriceInput = input.bool(true,         "Display Reversal Price")
  23. bool   showVolInput   = input.bool(true,         "Display Cumulative Volume")
  24. bool   showChgInput   = input.bool(true,         "Display Reversal Price Change",       inline = "Price Rev")
  25. string priceDiffInput = input.string("Absolute", "", options = ["Absolute", "Percent"], inline = "Price Rev")
  26. //#endregion
  27.  
  28.  
  29.  
  30. //#region ———————————————————— Library functions
  31.  
  32.  
  33. // @type                                Provides calculation and display attributes to ZigZag objects.
  34. // @field devThreshold                  The minimum percentage deviation from a point before the ZigZag will change direction.
  35. // @field depth                         The number of bars required for pivot detection.
  36. // @field lineColor                     Line color.
  37. // @field extendLast                    Condition allowing a line to connect the most recent pivot with the current close.
  38. // @field displayReversalPrice          Condition to display the pivot price in the pivot label.
  39. // @field displayCumulativeVolume       Condition to display the cumulative volume for the pivot segment in the pivot label.
  40. // @field displayReversalPriceChange    Condition to display the change in price or percent from the previous pivot in the pivot label.
  41. // @field differencePriceMode           Reversal change display mode. Options are "Absolute" or "Percent".
  42. // @field draw                          Condition to display lines and labels.
  43. export type Settings
  44.    float  devThreshold = 5.0
  45.    int    depth = 10
  46.    color  lineColor = #2962FF
  47.    bool   extendLast = true
  48.    bool   displayReversalPrice = true
  49.    bool   displayCumulativeVolume = true
  50.    bool   displayReversalPriceChange = true
  51.    string differencePriceMode = "Absolute"
  52.    bool   draw = true
  53.  
  54.  
  55. // @type                A coordinate containing bar, price, and time information.  
  56. // @field tm            A value in UNIX time.
  57. // @field price         A value on the Y axis (price).
  58. // @field barIndex      A `bar_index`.
  59. export type Point
  60.    int   tm
  61.    float price
  62.    int   barIndex
  63.  
  64.  
  65. // @type                A level of significance used to determine directional movement or potential support and resistance.
  66. // @field ln            A line object connecting the `start` and `end` Point objects.
  67. // @field lb            A label object to display pivot values.
  68. // @field isHigh        A condition to determine if the pivot is a pivot high.
  69. // @field isHigher      A condition to determine if the pivot is higher than previous (high or low) pivot
  70. // @field vol           Volume for the pivot segment.
  71. // @field start         The coordinate of the previous Point.
  72. // @field end           The coordinate of the current Point.
  73. export type Pivot
  74.    line  ln
  75.    label lb
  76.    bool  isHigh
  77.    bool  isHigher
  78.    float vol
  79.    Point start
  80.    Point end
  81.  
  82.  
  83. // @type                An object to maintain Zig Zag settings, pivots, and volume.
  84. // @field settings      Settings object to provide calculation and display attributes.
  85. // @field pivots        An array of Pivot objects.
  86. // @field sumVol        The volume sum for the pivot segment.
  87. // @field extend        Pivot object used to project a line from the last pivot to the last bar.
  88. export type ZigZag
  89.    Settings settings
  90.    array<Pivot> pivots
  91.    float sumVol = 0
  92.    Pivot extend = na
  93.  
  94.  
  95. // @function            Finds a pivot point if the `src` has not been exceeded over the `length` of bars. Finds pivot highs when `isHigh` is true, and pivot lows otherwise.
  96. // @param src           (series float) Data series to calculate the pivot value.
  97. // @param length        (series float) Length in bars required for pivot confirmation.
  98. // @param isHigh        (simple bool) Condition to determine if the Pivot is a pivot high or pivot low.
  99. // @returns             (Point) A Point object when a pivot is found, and `na` otherwise.
  100. findPivotPoint(series float src, series float length, simple bool isHigh) =>
  101.    float p = nz(src[length])
  102.    if length == 0
  103.        Point.new(time, p, bar_index)
  104.    else if length * 2 <= bar_index
  105.        bool isFound = true
  106.        for i = 0 to math.abs(length - 1)
  107.            if (isHigh and src[i] > p) or (not isHigh and src[i] < p)
  108.                isFound := false
  109.                break
  110.        for i = length + 1 to 2 * length
  111.            if (isHigh and src[i] >= p) or (not isHigh and src[i] <= p)
  112.                isFound := false
  113.                break
  114.        if isFound
  115.            Point.new(time[length], p, bar_index[length])
  116.  
  117.  
  118. // @function            Calculates the absolute deviation percentage between the `price` and the `basePrice`.
  119. // @param basePrice     (series float) Start price.
  120. // @param price         (series float) End price.
  121. // @returns             (float) Absolute deviation percentage.
  122. calcDev(series float basePrice, series float price) =>
  123.    float result = math.abs(100 * (price - basePrice) / basePrice)
  124.  
  125.  
  126. // @function            Calculates the difference between the `start` and `end` point as a price or percentage difference and converts the value to a string variable.
  127. // @param start         (series float) Start price.
  128. // @param end           (series float) End price.
  129. // @param settings      (series Settings) A Settings object.
  130. // @returns             (string) A string representation of the difference between points.
  131. priceRotationDiff(series float start, series float end, Settings settings) =>
  132.    float  diff    = end - start
  133.    string sign    = math.sign(diff) > 0 ? "+" : ""
  134.    string diffStr = settings.differencePriceMode == "Absolute" ? str.tostring(diff, format.mintick) : str.tostring(diff * 100 / start, format.percent)
  135.    string result  = str.format("({0}{1})", sign, diffStr)
  136.  
  137.  
  138. // @function            Creates a string variable containing the price, cumulative volume, and change in price for the pivot.  
  139. // @param start         (series float) Start price.
  140. // @param end           (series float) End price.
  141. // @param vol           (series float) Volume.
  142. // @param settings      (series Settings) A Settings object.
  143. // @returns             (string) A string to be displayed in pivot labels.
  144. priceRotationAggregate(series float start, series float end, series float vol, Settings settings) =>
  145.    string str = ""
  146.    if settings.displayReversalPrice
  147.        str += str.tostring(end, format.mintick) + " "
  148.    if settings.displayReversalPriceChange
  149.        str += priceRotationDiff(start, end, settings) + " "
  150.    if settings.displayCumulativeVolume
  151.        str += "\n" + str.tostring(vol, format.volume)
  152.    str
  153.  
  154.  
  155. // @function            Produces a label at the `p` Point if `settings` display attributes are enabled.
  156. // @param isHigh        (series bool) Condition to determine the label color and location.
  157. // @param p             (series Point) A Point object.
  158. // @param settings      (series Settings) A Settings object.
  159. // @returns             (void) Function has no return.
  160. makePivotLabel(series bool isHigh, Point p, Settings settings) =>
  161.    if settings.displayReversalPrice or settings.displayReversalPriceChange or settings.displayCumulativeVolume
  162.        [yloc, txtColor] = switch
  163.            isHigh => [yloc.abovebar, color.green]
  164.            =>        [yloc.belowbar, color.red]
  165.        label.new(p.tm, p.price, style = label.style_none, xloc = xloc.bar_time, yloc = yloc, textcolor = txtColor)
  166.  
  167.  
  168. // @function            Updates Pivot attributes including Point objects, volume, label text, and label and line object locations.
  169. // @param this          (series Pivot) Pivot object to be updated.
  170. // @param end           (series Point) Point to set the Pivot to.
  171. // @param vol           (series float) Volume of the Pivot.
  172. // @param settings      (series Settings) A Settings object.
  173. // @returns             (void) Function has no return.
  174. updatePivot(Pivot this, Point end, float vol, Settings settings) =>
  175.    this.end := end
  176.    this.vol := vol
  177.    if not na(this.lb)
  178.        label.set_xy(this.lb, this.end.tm, this.end.price)
  179.        label.set_text(this.lb, priceRotationAggregate(this.start.price, this.end.price, this.vol, settings))
  180.    line.set_xy2(this.ln, this.end.tm, this.end.price)
  181.  
  182.  
  183. // @function            Creates a new Pivot object and assigns a line and label if enabled in the `settings`.
  184. // @param start         (series Point) The start Point of the Pivot.
  185. // @param end           (series Point) The end Point of the Pivot.
  186. // @param vol           (series float) Volume of the Pivot.
  187. // @param isHigh        (series bool) Condition to determine if the Pivot is a pivot high or pivot low.
  188. // @param settings      (series settings) Settings object.
  189. // @returns             (Pivot) The new Pivot object.
  190. newPivot(series Point start, series Point end, series float vol, series bool isHigh, series Settings settings) =>
  191.    Pivot p = Pivot.new(na, na, isHigh, vol, start, end)
  192.    if settings.draw
  193.        p.ln := line.new(start.tm, start.price, end.tm, end.price, xloc = xloc.bar_time, color = settings.lineColor, width = 2)
  194.        p.lb := makePivotLabel(isHigh, end, settings)
  195.    updatePivot(p, end, vol, settings)
  196.    p
  197.  
  198.  
  199. // @function            Deletes line and label objects from `this` Pivot.
  200. // @param this          (series Pivot) A Pivot object.
  201. // @returns             (void) Function has no return.
  202. delete(series Pivot this) =>
  203.    if not na(this.ln)
  204.        line.delete(this.ln)
  205.        this.ln := na
  206.    if not na(this.lb)
  207.        label.delete(this.lb)
  208.        this.lb := na
  209.  
  210.  
  211. // @function            Determines if price of the `p` Point is greater than the end price of `this` Pivot.
  212. // @param this          (series Pivot) A Pivot object.
  213. // @param p             (series Point) A Point object.
  214. // @returns             (bool) true if the price of `p` is greater than `this` Pivot price.
  215. isMorePrice(series Pivot this, series Point p) =>
  216.    int m = this.isHigh ? 1 : -1
  217.    bool result = p.price * m > this.end.price * m
  218.  
  219.  
  220. // @function            Returns the last Pivot of `this` ZigZag if there is at least one Pivot to return, and `na` otherwise.
  221. // @param this          (series ZigZag) A ZigZag object.
  222. // @returns             (Pivot) The last Pivot in the ZigZag.
  223. export lastPivot(series ZigZag this) =>
  224.    int s = array.size(this.pivots)
  225.    Pivot result = s > 0 ? array.get(this.pivots, s - 1) : na
  226.  
  227.  
  228. // @function            Updates the last Pivot of `this` ZigZag to the `p` Point and sets the volume to 0.
  229. // @param this          (series ZigZag) A ZigZag object.
  230. // @param p             (series Point) The Point to set the Pivot to.
  231. // @returns             (void) Function has no return.
  232. updateLastPivot(series ZigZag this, series Point p) =>
  233.    Pivot lastPivot = lastPivot(this)
  234.    if array.size(this.pivots) == 1
  235.        lastPivot.start := p
  236.        if this.settings.draw
  237.            line.set_xy1(lastPivot.ln, p.tm, p.price)
  238.    updatePivot(lastPivot, p, lastPivot.vol + this.sumVol, this.settings)
  239.    this.sumVol := 0
  240.  
  241.  
  242. // @function            Pushes a `new` Pivot into the array within `this` ZigZag.
  243. // @param this          (series ZigZag) A ZigZag object.
  244. // @param new           (series Pivot) The Pivot to add to the ZigZag.
  245. // @returns             (void) Function has no return.
  246. newPivotFound(series ZigZag this, series Pivot new) =>
  247.    array.push(this.pivots, new)
  248.    this.sumVol := 0
  249.  
  250.  
  251. // @function            Determines if a new ZigZag line has been found or the existing line needs updating by comparing new pivots to the existing ZigZag Point. Updates `this` ZigZag and returns true if either condition occurs.
  252. // @param this          (series ZigZag) A ZigZag object.      
  253. // @param isHigh        (series bool) Condition to look for pivot high or pivot low.
  254. // @param p             (Point) A Point object to compare to the current ZigZag Point.
  255. // @returns             (bool) true if a new ZigZag line is found or last zigzag line has changed.
  256. newPivotPointFound(series ZigZag this, simple bool isHigh, series Point p) =>
  257.    bool result = false
  258.    Pivot lastPivot = lastPivot(this)
  259.    if not na(lastPivot)
  260.        if lastPivot.isHigh == isHigh
  261.            if isMorePrice(lastPivot, p)
  262.                updateLastPivot(this, p)
  263.                result := true
  264.        else
  265.            if calcDev(lastPivot.end.price, p.price) >= this.settings.devThreshold
  266.                newPivotFound(this, newPivot(lastPivot.end, p, this.sumVol, isHigh, this.settings))
  267.                result := true
  268.    else
  269.        newPivotFound(this, newPivot(p, p, this.sumVol, isHigh, this.settings))
  270.        result := true
  271.    result
  272.  
  273.  
  274. // @function            Determines if a new ZigZag Point has been found.
  275. // @param this          (series ZigZag) a ZigZag object.  
  276. // @param src           (series float) Data series to calculate the pivot.
  277. // @param isHigh        (simple bool) Condition to look for pivot high or pivot low.
  278. // @param depth         (series int) The length of bars to look for pivots.
  279. // @returns             (bool) true if a new Zig Zag line is found or the last Zig Zag line has changed.
  280. tryFindPivot(series ZigZag this, series float src, simple bool isHigh, series int depth) =>
  281.    Point point = findPivotPoint(src, depth, isHigh)
  282.    bool result = not na(point) ? newPivotPointFound(this, isHigh, point) : false
  283.  
  284.  
  285. // @function            Updates `this` ZigZag object with new pivots, volume, lines, labels. NOTE: The function must be called on every bar for accurate calculations.  
  286. // @param this          (series ZigZag) a ZigZag object.        
  287. // @returns             (bool) true if a new Zig Zag line is found or the last Zig Zag line has changed.
  288. export update(series ZigZag this) =>
  289.    int depth = math.floor(this.settings.depth / 2)
  290.    this.sumVol += nz(volume[depth])
  291.    bool somethingChanged = tryFindPivot(this, high, true, depth) or tryFindPivot(this, low, false, depth)
  292.    Pivot lastPivot = lastPivot(this)
  293.    float remVol = math.sum(volume, math.max(depth, 1))
  294.    if this.settings.extendLast and barstate.islast and not na(lastPivot)
  295.        bool isHigh = not lastPivot.isHigh
  296.        bool isHigher = array.size(pivots) > 2  ? lastPivot.end.price > array.get(pivots, array.size(pivots) -3).end.price : na
  297.        float curSeries = isHigh ? high : low
  298.        Point end = Point.new(time, curSeries, bar_index)
  299.        if na(this.extend) or somethingChanged
  300.            if not na(this.extend)
  301.                delete(this.extend)
  302.            this.extend := newPivot(lastPivot.end, end, this.sumVol, isHigh, this.settings)
  303.        updatePivot(this.extend, end, this.sumVol + remVol, this.settings)
  304.    somethingChanged
  305.  
  306.  
  307. // @function            Instantiates a new ZigZag object with `settings`. If no settings are provided, a default ZigZag object is created.
  308. // @param settings      (series Settings) A Settings object.
  309. // @returns             (ZigZag) A new ZigZag instance.
  310. export newInstance(series Settings settings = na) =>
  311.    ZigZag result = ZigZag.new(na(settings) ? Settings.new() : settings, array.new<Pivot>())
  312. //#endregion
  313.  
  314.  
  315.  
  316. //#region ———————————————————— Example Code
  317.  
  318. var Settings settings =
  319. Settings.new(
  320. deviationInput, depthInput,
  321. lineColorInput, extendInput,
  322. showPriceInput, showVolInput,
  323. showChgInput,   priceDiffInput)
  324.  
  325. var ZigZag zigZag = newInstance(settings)
  326. update(zigZag)
  327. //#endregion
  328.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement