/ Plotting / src / ohlcv.jl
ohlcv.jl
  1  using .ect.TimeTicks
  2  using .ect.Strategies.Data
  3  using .Data.DataFrames
  4  using .Data.DFUtils
  5  using .Data: AbstractDataFrame
  6  using .Data.DataFramesMeta
  7  using Processing: resample
  8  
  9  @doc """ Returns a formatted asset string or an empty string if the asset is `nothing` """
 10  maybe_asset(a) = isnothing(a) ? "" : "Asset: $(a)\n"
 11  @doc """ Returns a formatted candle string """
 12  function candle_str(row, asset=nothing)
 13      """$(maybe_asset(asset))O: $(cn(row.open))
 14      H: $(cn(row.high))
 15      L: $(cn(row.low))
 16      C: $(cn(row.close))
 17      V: $(cn(row.volume))
 18      T: $(row.timestamp)"""
 19  end
 20  
 21  @doc """ Returns a function for generating candle tooltips
 22  
 23  $(TYPEDSIGNATURES)
 24  
 25  The function returns a function that, when called with an inspector, plot, and index, generates a tooltip for a candlestick chart.
 26  The tooltip includes information about the open, high, low, close, volume, and timestamp of the candle at the given index.
 27  """
 28  function candle_tooltip_func(df)
 29      function f(inspector, plot, idx, _)
 30          try
 31              idx = tooltip_position!(inspector, plot, idx; vertices=4)
 32              tooltip_text!(inspector, candle_str(df[idx, :]); size=3.0)
 33          catch
 34          end
 35          return true
 36      end
 37  end
 38  
 39  @doc """ Returns a set of points representing a volume bar in a plot
 40  
 41  $(TYPEDSIGNATURES)
 42  
 43  The function takes a `width`, `x` (position), and `y` (height) as parameters and returns a set of points that represent a volume bar in a plot.
 44  """
 45  function vol_point(width, x, y)
 46      Point2f[(x - width, 0.0), (x + width, 0.0), (x + width, y), (x - width, y)]
 47  end
 48  @doc """ Returns a set of points representing a candlestick in a plot
 49  
 50  $(TYPEDSIGNATURES)
 51  
 52  The function takes a `width`, `x` (position), `y1` (open/close price), and `y2` (high/low price) as parameters and returns a set of points that represent a candlestick in a plot.
 53  If `y1` equals `y2`, `y1` is slightly increased to ensure visibility.
 54  """
 55  function ohlcv_point(width, x, y1, y2)
 56      y1 == y2 && (y1 += y1 * 0.01)
 57      Point2f[(x - width, y1), (x + width, y1), (x + width, y2), (x - width, y2)]
 58  end
 59  
 60  @doc """ Plots ohlcv data from dataframe `df`, resampling to `tf`
 61  
 62  $(TYPEDSIGNATURES)
 63  
 64  The function takes a dataframe `df` and a time frame `tf` as parameters.
 65  If `tf` is not `nothing`, the dataframe is resampled according to the time frame.
 66  """
 67  ohlcv(df::AbstractDataFrame, tf=tf"1d"; kwargs...) = ohlcv!(makefig(), df, tf; kwargs...)
 68  
 69  # function definescaler(f; limits=(0.0, 100.0), interval=-Inf .. Inf)
 70  #     @eval begin
 71  #         Makie.defaultlimits(::typeof($f)) = $limits
 72  #         Makie.defined_interval(::typeof($f)) = $interval
 73  #     end
 74  # end
 75  
 76  @doc """ Plots ohlcv data from dataframe `df`, resampling to `tf` on an existing figure
 77  
 78  $(TYPEDSIGNATURES)
 79  
 80  The function takes a figure `fig`, a dataframe `df`, and a time frame `tf` as parameters.
 81  If `tf` is not `nothing`, the dataframe is resampled according to the time frame.
 82  The function then plots the ohlcv data on the provided figure.
 83  """
 84  function ohlcv!(fig::Figure, df::AbstractDataFrame, tf=tf"1d")
 85      isnothing(tf) || (df = resample(df, timeframe!(df), tf))
 86      # Axis creation (Order is important)
 87      vol_ax = Axis(fig[1, 1]; ytickformat=ytickscompact, ylabel="Volume", yaxisposition=:right)
 88      price_ax = makepriceax(fig; xticksargs=(df.timestamp[begin], timeframe!(df)))
 89      # OHLCV doesn't need spines
 90      hidespines!(price_ax)
 91      hidespines!(vol_ax)
 92      # We already have X from the price axis
 93      hidexdecorations!(vol_ax)
 94      # Disable volume interactions since it is in the background
 95      deregister_interactions!(vol_ax, (:scrollzoom, ))
 96  
 97      # Now we iterate over the dataframe, to construct
 98      # candles polygons from their values
 99      # we use a wider one for open/close
100      # and a thinner rectangle for high/low
101      width = 0.4 # candle width
102      makevec(type) = Vector{type}(undef, nrow(df))
103      oc_points = makevec(Vector{Point2f})
104      hl_points = makevec(Vector{Point2f})
105      vol_points = makevec(Vector{Point2f})
106      colors = makevec(Symbol)
107      # FIXME: Can't seem able to escape symbols using ^() syntax in the macro
108      let n = 1, red = :red, green = :green
109          @eachrow df begin
110              date = n
111              colors[n] = :open < :close ? green : red
112              oc_points[n] = ohlcv_point(width, date, :open, :close)
113              hl_points[n] = ohlcv_point(width / 10.0, date, :high, :low)
114              vol_points[n] = vol_point(width, date, :volume)
115              n += 1
116          end
117      end
118      # get the candle tooltip function, for the DF we are plotting
119      candle_tooltip = candle_tooltip_func(df)
120      poly_kwargs = (; inspector_hover=candle_tooltip) # inspector_clear=candle_tooltip_clear)
121      # Constructs all the polygons from the candles Points (order is important)
122      poly!(vol_ax, vol_points; color=(:grey, 0.5), inspectable=false, poly_kwargs...)
123      poly!(price_ax, hl_points; color=colors, inspectable=false, poly_kwargs...)
124      poly!(price_ax, oc_points; color=colors, poly_kwargs...)
125      # Ansure that volume and price axies are linked
126      # such that when we zoom/pan they move together
127      # But only link x axes to keep vol always in the background when zooming
128      linkxaxes!(price_ax, vol_ax)
129  
130      # Reduce the size of the volume axis which will appear
131      # smaller at the bottom
132      # rowsize!(fig.layout, 2, Aspect(1, 0.05))
133      # Make sure the volume and price axis are close together
134      # rowgap!(fig.layout, 1, 0.0)
135      # Enables the tooltip function on the figure
136      di = DataInspector(price_ax; priority=-1)
137      fig.attributes[:inspector] = di
138      fig
139  end
140  
141  export ohlcv, ohlcv!