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!