From cd7cc2030f90f63150d0cd9e204db9f56b19f225 Mon Sep 17 00:00:00 2001 From: Vikas Negi <68782261+vnegi10@users.noreply.github.com> Date: Sat, 25 Jun 2022 20:38:55 +0200 Subject: [PATCH] Add bollinger bands (#16) * Plot Bollinger bands * Add mode for Bollinger bands * Add mode for Bollinger bands * Fix Bollinger plot * Add test for price std * Remove extra lines + update docs * Fix typo --- docs/src/index.md | 22 +++++++++---- src/ConfigureApp.jl | 35 ++++++++++++++++---- src/CryptoDashApp.jl | 2 +- src/CryptoFunctions.jl | 19 +++++++++-- src/GetDataFunctions.jl | 23 +------------ src/PlotFunctions.jl | 71 +++++++++++++++++++++++------------------ test/runtests.jl | 13 +++----- 7 files changed, 107 insertions(+), 78 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 0abde0d..d7c387a 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -43,6 +43,20 @@ The plots are interactive, which means you can hover your cursor over the data p - Plot title also contains $R^2$, which is a statistical measure representing the proportion of variation in the dependent variable that is explained by different features (independent variables) in this model. A value closer to 1 (maximum) indicates a good fit. The linear regression channel metric will therefore not be very useful when $R^2 < 0.50$. +### Bollinger bands +- These are price envelopes plotted at two standard deviations (std) above (upper band) and below +(lower band) a simple moving average of the daily price. Since the distance towards the bands is +based on std, it adjusts in response to the price volatility. + +- When the bands move closer (tighten during low volatility), it raises the chances of a sharp +price movement in either direction. On the contrary, diverging bands indicate high volatility, +which might also indicate the end for any existing trend. + +- Prices often bounce between the two bands, which could be used to identify potential price +targets. For example, when a price bounces off of the lower band, and crosses the moving average, +the upper band might be considered as the next profit target. Prices can also cross the bands +during strong trends. + ### Fundamental Cryptocurrency Asset Score (FCAS) data - This metric tells us about the market health of an asset. In the case of cryptocurrencies, they are user activity, developer behavior, and market maturity, which are provided by [Flipside Crypto](https://app.flipsidecrypto.com/tracker/all-coins). @@ -65,10 +79,4 @@ The plots are interactive, which means you can hover your cursor over the data p ## Run app ```@docs run_app(port::Int64, key::String) -``` - - - - - - +``` \ No newline at end of file diff --git a/src/ConfigureApp.jl b/src/ConfigureApp.jl index 2199b1a..7ca4f6b 100644 --- a/src/ConfigureApp.jl +++ b/src/ConfigureApp.jl @@ -7,10 +7,18 @@ currencies_list = ["BTC", "LTC", "BCH", "ETH", "KNC", "LINK", "ETC", "BNB", "ADA currencies = sort(currencies_list) currencies_index = 1:length(currencies) -modes = ["Average price + Daily trade (AV)", "Candlestick + Volume (AV)", - "Cumulative + Daily return (AV)", "Daily volatility (AV)", "MACD + Signal (AV)", - "Linear regression channel (AV)", "FCAS data (AV)", "Developer + Community data (CG)", - "Exchange volume data per currency (CG)", "Overall exchange volume data (CG)"] +modes = ["Average price + Daily trade (AV)", + "Candlestick + Volume (AV)", + "Cumulative + Daily return (AV)", + "Daily volatility (AV)", + "MACD + Signal (AV)", + "Linear regression channel (AV)", + "Bollinger bands (AV)", + "FCAS data (AV)", + "Developer + Community data (CG)", + "Exchange volume data per currency (CG)", + "Overall exchange volume data (CG)"] + modes_index = 1:length(modes) durations = [7, 14, 30, 90, 180, 270, 365, 500, 750, 1000] @@ -282,6 +290,19 @@ function run_app(port::Int64, key::String) return [P1] elseif mode_ID == 7 + t1, t2, t3, t4 = plot_price_bollinger_bands(pair_ID, duration_ID, window_ID) + + layout1 = Layout(;title="Bollinger bands for $(currencies[pair_ID])", + xaxis = attr(title="Time", showgrid=true, zeroline=true), + yaxis = attr(title="Price [euros]", showgrid=true, zeroline=true), + height = 500, + width = 1000, + ) + + P1 = Plot([t1, t2, t3, t4], layout1) # plots Bollinger bands + return [P1] + + elseif mode_ID == 8 t1, fr = plot_fcas_data(pair_ID) layout1 = Layout(;title="FCAS metrics data for $(currencies[pair_ID]), overall rating = $(fr)", @@ -295,7 +316,7 @@ function run_app(port::Int64, key::String) return [P1] # From CoinGecko - elseif mode_ID == 8 + elseif mode_ID == 9 t1, t2 = plot_dev_comm_data(pair_ID) layout1 = Layout(;title="Developer metrics for $(currencies[pair_ID])", @@ -317,7 +338,7 @@ function run_app(port::Int64, key::String) P2 = Plot(t2, layout2) # plots community data return [P1 P2] - elseif mode_ID == 9 + elseif mode_ID == 10 t1, t2 = plot_exchange_vol_data(pair_ID) @@ -340,7 +361,7 @@ function run_app(port::Int64, key::String) P2 = Plot(t2, layout2) # plots USD volume data for a given currency return [P1 P2] - elseif mode_ID == 10 + elseif mode_ID == 11 t_all = plot_overall_vol_data(duration_ID) diff --git a/src/CryptoDashApp.jl b/src/CryptoDashApp.jl index 6f684ae..a79b99f 100644 --- a/src/CryptoDashApp.jl +++ b/src/CryptoDashApp.jl @@ -11,4 +11,4 @@ include("GetDataFunctions.jl") include("PlotFunctions.jl") include("ConfigureApp.jl") -end # module +end # module \ No newline at end of file diff --git a/src/CryptoFunctions.jl b/src/CryptoFunctions.jl index 829a177..499c43a 100644 --- a/src/CryptoFunctions.jl +++ b/src/CryptoFunctions.jl @@ -74,6 +74,22 @@ function moving_averages(Price_df::DataFrame, duration::Int64, window::Int64) return Price_SMA, Price_WMA, Price_EMA end +function moving_std(Price_df::DataFrame, duration::Int64, window::Int64) + + # Price_df should have date order - oldest to latest + Price_col = Price_df[end-duration+1-window+1:end,2] + rows1 = length(Price_col) + + Price_std = Float64[] + + for i = 1:rows1-(window-1) + # Standard deviation over the period SMA is also being calculated + push!(Price_std, std(Price_col[i:i+(window-1)])) + end + + return Price_std +end + function calculate_ema(Price_col::Vector{Float64}, window::Int64) k(window) = 2/(window+1) @@ -117,5 +133,4 @@ function calculate_macd(Price_df::DataFrame, window_long::Int64 = 26, MACD = MACD_col[window_signal:end], Signal = Signal_col) return df_ema -end - +end \ No newline at end of file diff --git a/src/GetDataFunctions.jl b/src/GetDataFunctions.jl index 2416129..3a9e916 100644 --- a/src/GetDataFunctions.jl +++ b/src/GetDataFunctions.jl @@ -422,25 +422,4 @@ function get_overall_vol_data(duration::Int64, num_exchanges::Int64) return DataFrame() end -end - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file +end \ No newline at end of file diff --git a/src/PlotFunctions.jl b/src/PlotFunctions.jl index b5f03c7..69bc84d 100644 --- a/src/PlotFunctions.jl +++ b/src/PlotFunctions.jl @@ -26,6 +26,45 @@ function plot_price_ma_trade_data(index::Int64, duration::Int64, window::Int64) return trace1, trace2, trace3, trace4, trace5 end +function plot_price_bollinger_bands(index::Int64, duration::Int64, window::Int64) + + Price_df, _ , _ = get_price_data_single(currencies[index]) + + if duration > size(Price_df)[1]-maximum(windows) + duration = size(Price_df)[1]-maximum(windows) + end + + sort!(Price_df, :Date) # oldest date first, newest at the bottom + Price_SMA, _ , _ = moving_averages(Price_df, duration, window) + Price_σ = moving_std(Price_df, duration, window) + + ################# Raw price data ################# + + trace1 = PlotlyJS.scatter(;x = Price_df[!,:Date][end-length(Price_SMA)+1:end], + y = Price_df[!,2][end-length(Price_SMA)+1:end], + mode = "markers+lines", + name = "$(currencies[index]) price") + + ################# SMA and Bollinger bands ################# + + trace2 = PlotlyJS.scatter(;x = Price_df[!,:Date][end-length(Price_SMA)+1:end], + y = Price_SMA, + mode = "lines", + name = "$(names(Price_df)[2]) SMA over $(window) days") + + trace3 = PlotlyJS.scatter(;x = Price_df[!,:Date][end-length(Price_SMA)+1:end], + y = Price_SMA .+ 2*Price_σ, + mode = "markers", + name = "Upper band (+2σ)") + + trace4 = PlotlyJS.scatter(;x = Price_df[!,:Date][end-length(Price_SMA)+1:end], + y = Price_SMA .- 2*Price_σ, + mode = "markers", + name = "Lower band (-2σ)") + + return trace1, trace2, trace3, trace4 +end + function plot_candle_vol_data(index::Int64, duration::Int64) # Retrieve data from various helper functions @@ -293,34 +332,4 @@ function plot_overall_vol_data(duration::Int64, num_exchanges::Int64 = 10) end return all_traces -end - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 072915c..27be9a0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,7 +39,7 @@ end ################# Test cases for moving averages ################# -@testset "Check if MA, MACD and signal are calculated" begin +@testset "Check if MA, MACD + signal, σ (for Bollinger bands) are calculated" begin for currency in ["BTC", "DOT"] @@ -56,8 +56,10 @@ end df_out_price = df_out_price[end-duration+1-26-9+1:end, :] df_ema_all = CryptoDashApp.calculate_macd(df_out_price) - @test ~isempty(df_ema_all) + + Price_σ = CryptoDashApp.moving_std(df_out_price, duration, window) + @test ~isempty(Price_σ) end @@ -125,9 +127,4 @@ end @test ~isempty(dev_score) @test ~isempty(mark_score) @test ~isempty(fcas_rating) -end=# - - - - - +end=# \ No newline at end of file