Identify Range levels OHLC in Python - python

I need help in calculating levels based on OHLC data in python, Please find the sample code below. The output expected is of all the price levels with datetime, which would help me plot ranges on the charts. More detailing are commented as part of the code. Any help here would be really helpfull.
import pandas as pd
import plotly.graph_objs as go
import plotly.express as px
df = pd.read_csv("https://raw.githubusercontent.com/IRPK16/SampleShare/5372e0c9fe07f6e31ac2729c86209684f6af69d1/CADCHF.csv")
df['Date'] = pd.to_datetime(df['Date'])
fig = go.Figure(data=go.Candlestick(x=df['Date'],
open=df['Open'],
high=df['High'],
low=df['Low'],
close=df['Close']))
fig.update(layout_xaxis_rangeslider_visible=False)
fig.update_layout(
title='CADCHF',
yaxis_title='Price',
xaxis_title='Date')
# Scenario 1: Identify normal range, currently hardcoded for last 20 bars. Need help in getting array[range1, range2,...] programatically
range_line = df.iloc[-22:].copy()
range_line['min_line'] = range_line['Close'].min()
range_line['mid_line'] = range_line['Close'].mean()
range_line['max_line'] = range_line['Close'].max()
fig.add_trace(
go.Scatter(mode = 'lines', x=range_line['Date'], y=range_line['min_line'], line={'color':'black', 'width':1}
))
fig.add_trace(
go.Scatter(mode = 'lines', x=range_line['Date'], y=range_line['max_line'], line={'color':'black', 'width':1}
))
fig.add_trace(
go.Scatter(mode = 'lines', x=range_line['Date'], y=range_line['mid_line'], line={'color':'black', 'width':1}
))
print(df)
# Scenario 2: Identify squeeze range, currently hardcoded for last 53 to 62 bars. Need help in getting such array[range1, range2,...] programatically
range_line2 = df.iloc[53:62].copy()
range_line2['min_line'] = range_line2['Close'].min()
range_line2['mid_line'] = range_line2['Close'].mean()
range_line2['max_line'] = range_line2['Close'].max()
fig.add_trace(
go.Scatter(mode = 'lines', x=range_line2['Date'], y=range_line2['mid_line'], line={'color':'black', 'width':1}
))
fig.update_traces(showlegend=False)
fig.show()
expected output is as below:
def detect_level_method_2(df):
levels = []
max_list = []
min_list = []
for i in range(5, len(df)-5):
high_range = df['High'][i-5:i+4]
current_max = high_range.max()
if current_max not in max_list:
max_list = []
max_list.append(current_max)
if len(max_list) == 5 and isFarFromLevel(current_max, levels, df):
levels.append((high_range.idxmax(), current_max))
low_range = df['Low'][i-5:i+5]
current_min = low_range.min()
if current_min not in min_list:
min_list = []
min_list.append(current_min)
if len(min_list) == 5 and isFarFromLevel(current_min, levels, df):
levels.append((low_range.idxmin(), current_min))
return levels
Update:
I am able to identify based on previous value, but unable to group the values based on close nearest range. Please help me in solving this problem.
df = pd.read_csv("https://raw.githubusercontent.com/IRPK16/SampleShare/5372e0c9fe07f6e31ac2729c86209684f6af69d1/CADCHF.csv")
df['Date'] = pd.to_datetime(df['Date'])
dfOrderedByDateDesc = df.sort_values(['Date'], ascending=[True])
bar = dfOrderedByDateDesc.iloc[-1:].copy() # Returns last bar
from statistics import median
df.set_index('Date', inplace=True)
df['Prev_High'] = df['High'].shift()
df['Prev_Low'] = df['Low'].shift()
df['isInRange'] = ((df['Close'] < df['Prev_High']) & (df['Close'] > df['Prev_Low']))
print(df.tail(3))

Related

Adding counts to Plotly boxplots

I have a relatively simple issue, but cannot find any answer online that addresses it. Starting from a simple boxplot:
import plotly.express as px
df = px.data.iris()
fig = px.box(
df, x='species', y='sepal_length'
)
val_counts = df['species'].value_counts()
I would now like to add val_counts (in this dataset, 50 for each species) to the plots, preferably on either of the following places:
On top of the median line
On top of the max/min line
Inside the hoverbox
How can I achieve this?
The snippet below will set count = 50 for all unique values of df['species'] on top of the max line using fig.add_annotation like this:
for s in df.species.unique():
fig.add_annotation(x=s,
y = df[df['species']==s]['sepal_length'].max(),
text = str(len(df[df['species']==s]['species'])),
yshift = 10,
showarrow = False
)
Plot:
Complete code:
import plotly.express as px
df = px.data.iris()
fig = px.box(
df, x='species', y='sepal_length'
)
for s in df.species.unique():
fig.add_annotation(x=s,
y = df[df['species']==s]['sepal_length'].max(),
text = str(len(df[df['species']==s]['species'])),
yshift = 10,
showarrow = False
)
f = fig.full_figure_for_development(warn=False)
fig.show()
Using same approach that I presented in this answer: Change Plotly Boxplot Hover Data
calculate all the measures a box plot calculates plus the additional measure you want count
overlay bar traces over box plot traces so hover has all measures required
import plotly.express as px
df = px.data.iris()
# summarize data as per same dimensions as boxplot
df2 = df.groupby("species").agg(
**{
m
if isinstance(m, str)
else m[0]: ("sepal_length", m if isinstance(m, str) else m[1])
for m in [
"max",
("q75", lambda s: s.quantile(0.75)),
"median",
("q25", lambda s: s.quantile(0.25)),
"min",
"count",
]
}
).reset_index().assign(y=lambda d: d["max"] - d["min"])
# overlay bar over boxplot
px.bar(
df2,
x="species",
y="y",
base="min",
hover_data={c:not c in ["y","species"] for c in df2.columns},
hover_name="species",
).update_traces(opacity=0.1).add_traces(px.box(df, x="species", y="sepal_length").data)

Change Plotly Boxplot Hover Data

I want to change the hover text and hover data for a python plotly boxplot. Instead of 5 separate hover boxes for max, q3, median, q1, and min, I want one condensed hover box for Median, Mean, IQR and date. I have played around with every "hover" variable with no luck. My sample code is found below.
import numpy as np
import plotly.express as px
lst = [['2020'], ['2021']]
numbers = [20 , 25]
r = [x for i, j in zip(lst, numbers) for x in i*j]
df = pd.DataFrame(r, columns=['year'])
df['obs'] = np.arange(1,len(df)+1) * np.random.random()
mean = df.groupby('year').mean()[['obs']]
median = df.groupby('year').median()[['obs']]
iqr = df.groupby('year').quantile(0.75)[['obs']] - df.groupby('year').quantile(0.25)[['obs']]
stats = pd.concat([mean,median,iqr], axis=1)
stats.columns = ['Mean','Median','IQR']
tot_df = pd.merge(df,stats, right_index=True, left_on='year', how = 'left')
fig = px.box(tot_df, x="year", y="obs", points=False, hover_data=['year','Mean','Median','IQR'])
fig.show()
In this case I tried to use "hover_data", which does not raise an error, but also does not change the plot, as shown above. I have tried both express and graph_objects with no luck. My plotly versions is 4.9.0. Thank you!
have used technique of overlaying a bar trace over boxplot trace
bar trace can be configured to show information you want
for sake of demonstration, I have set opacity to 0.05 it can be set to 0 to make it fully invisible
have built this against plotly 5.2.1, have not tested against 4.9.0
import numpy as np
import plotly.express as px
import pandas as pd
lst = [['2020'], ['2021']]
numbers = [20 , 25]
r = [x for i, j in zip(lst, numbers) for x in i*j]
df = pd.DataFrame(r, columns=['year'])
df['obs'] = np.arange(1,len(df)+1) * np.random.random()
mean = df.groupby('year').mean()[['obs']]
median = df.groupby('year').median()[['obs']]
iqr = df.groupby('year').quantile(0.75)[['obs']] - df.groupby('year').quantile(0.25)[['obs']]
stats = pd.concat([mean,median,iqr], axis=1)
stats.columns = ['Mean','Median','IQR']
tot_df = pd.merge(df,stats, right_index=True, left_on='year', how = 'left')
fig = px.box(tot_df, x="year", y="obs", points=False)
fig2 = px.bar(
tot_df.groupby("year", as_index=False)
.agg(base=("obs", "min"), bar=("obs", lambda s: s.max() - s.min()))
.merge(
tot_df.groupby("year", as_index=False).agg(
{c: "first" for c in tot_df.columns if c not in ["year", "obs"]}
),
on="year",
),
x="year",
y="bar",
base="base",
hover_data={
**{c: True for c in tot_df.columns if c not in ["year", "obs"]},
**{"base": False, "bar": False},
},
).update_traces(opacity=0.05)
fig.add_traces(fig2.data)
fig2 without named aggregations
fig2 = px.bar(
tot_df.groupby("year", as_index=False)["obs"]
.apply(lambda s: pd.Series({"base": s.min(), "bar": s.max() - s.min()}))
.merge(
tot_df.groupby("year", as_index=False).agg(
{c: "first" for c in tot_df.columns if c not in ["year", "obs"]}
),
on="year",
),
x="year",
y="bar",
base="base",
hover_data={
**{c: True for c in tot_df.columns if c not in ["year", "obs"]},
**{"base": False, "bar": False},
},
).update_traces(opacity=0.05)

How to set bar labels in stack barmode grouped by date?

This code produces the figure I've attached. Notice the sums are the totals over the df, but I need the columns to only show the totals for that particular month. What do you have to set in the
text = ...
assignment for this to occur?
df = data[['Month', 'A', 'B']]
for X in df['A'].unique():
trace = go.Bar(
x = df[df['A']==X]['Month'],
y = df[df['A']==X]['B'],
text = str(df[df['A']==X]['B'].sum())
)
traces.append(trace)
df = data.groupby(['Month','TA']).sum().reset_index()
for TA in df['TA'].unique():
trace = go.Bar(
x = df[df['TA']==TA]['Month'],
y = df[df['TA']==TA]['Studies'],
text = df[df['TA']==TA]['Studies'],
name = TA
)
traces.append(trace)
As long as all values are already showing in your figure, the following will work regardless of how you've built your figure or grouped your data:
numbers = []
fig.for_each_trace(lambda t: numbers.append([float(nr) for nr in t.text]))
sums = [sum(i) for i in zip(*numbers)]
for i,d in enumerate(fig.data):
if i == len(fig.data)-1:
d.text = sums
else:
d.text = ''
fig.show()
Result:
Example of original figure:
Complete code:
# imports
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
# data
df = px.data.stocks()
df = df[df.columns[:3]]
df = df.tail(25)
df['date'] = pd.to_datetime(df['date'])
# group py month
dfm = df.groupby(pd.Grouper(key = 'date', freq='M')).agg('sum').reset_index()
# figure setup
fig = go.Figure()
for col in dfm.columns[1:]:
fig.add_trace(go.Bar(x=dfm.date, y = dfm[col], text = [str(v)[:3] for v in dfm[col]], textposition = 'auto'))
fig.update_layout(barmode = 'stack')
# grap and sum data for all bars
numbers = []
fig.for_each_trace(lambda t: numbers.append([float(nr) for nr in t.text]))
sums = [sum(i) for i in zip(*numbers)]
for i,d in enumerate(fig.data):
if i == len(fig.data)-1:
d.text = sums
else:
d.text = ''
fig.show()

How to add labels to subplots in plotly?

I am trying to plot a candlestick with volume, using the plotly. However I can not get the proper x and yaxis label.please help.I need y labels for both plot but xlabel for just the bottom one, also one title for both. Bellow is the code.
** one more question, how can I change the line color in the volume plot.Thank you
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from plotly import tools
stock = 'AAPL'
df = web.DataReader(stock, data_source='yahoo', start='01-01-2019')
def chart_can_vol(df):
fig = tools.make_subplots(
rows=3, cols=1,
specs=[[{"rowspan": 2}],
[None],
[{}]],
shared_xaxes=True,
vertical_spacing=0.1)
fig.add_trace(go.Candlestick(x = df.index,
open = df['Open'],
close = df['Close'],
low = df['Low'],
high = df['High']),
row = 1, col = 1)
fig.update_layout(xaxis_rangeslider_visible = False)
fig.update_layout(
yaxis_title = 'Apple Stock Price USD ($)'
)
fig.add_trace(go.Scatter(x = df.index,
y = df['Volume']),
row = 3, col = 1)
fig.update_layout(
yaxis_title = 'Volume',
xaxis_title = 'Date'
)
fig.update_layout(title_text="Apple Stock")
fig.update_layout(width=900, height=900)
return fig
chart_can_vol(df)
When you make your subplots, you can add the subplot_titles attribute. In the code below, I used the titles "test1" and "test2". When you change your axis labels, you can use update_xaxes and update_yaxes, just make sure that the row and column values are the same for the update_axes method and the subplot.
To change the color of the line, you can add the line attribute within the scatterplot method and set it equal to a dictionary with a hex value of the color you want.
P.S. You should update plotly, because the tools.make_subplots was deprecated. Once you update, you can simply use make_subplots. Also, you are using pandas, when you should use pandas-datareader. See import statements.
Code:
import numpy as np
import pandas as pd
import pandas_datareader.data as web
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from plotly import tools
stock = 'AAPL'
df = web.DataReader(stock, data_source='yahoo', start='01-01-2019')
def chart_can_vol(df):
subplot_titles=["test1", "test2"]
rows = 2
cols = 2
height = 300 * rows
fig = make_subplots(
rows=3, cols=1,
specs=[[{"rowspan": 2}],
[None],
[{}]],
shared_xaxes=True,
subplot_titles=("test1", "test2"),
vertical_spacing=0.1)
fig.add_trace(go.Candlestick(x = df.index,
open = df['Open'],
close = df['Close'],
low = df['Low'],
high = df['High']),
row = 1, col = 1)
fig.update_layout(xaxis_rangeslider_visible = False)
fig.update_layout(
yaxis_title = 'Apple Stock Price USD ($)'
)
fig.add_trace(go.Scatter(x = df.index,
y = df['Volume'],
line= dict(color="#ffe476")),
row = 3, col = 1)
fig.update_xaxes(title_text="Date", row = 3, col = 1)
fig.update_yaxes(title_text="Volume", row = 3, col = 1)
fig.update_layout(title_text="Apple Stock")
fig.update_layout(width=900, height=900)
return fig
chart_can_vol(df).show()

how to plot a range with a line in the center with Plotly, in Python [duplicate]

How can I use Plotly to produce a line plot with a shaded standard deviation? I am trying to achieve something similar to seaborn.tsplot. Any help is appreciated.
The following approach is fully flexible with regards to the number of columns in a pandas dataframe and uses the default color cycle of plotly. If the number of lines exceed the number of colors, the colors will be re-used from the start. As of now px.colors.qualitative.Plotly can be replaced with any hex color sequence that you can find using px.colors.qualitative:
Alphabet = ['#AA0DFE', '#3283FE', '#85660D', '#782AB6', '#565656', '#1...
Alphabet_r = ['#FA0087', '#FBE426', '#B00068', '#FC1CBF', '#C075A6', '...
[...]
Complete code:
# imports
import plotly.graph_objs as go
import plotly.express as px
import pandas as pd
import numpy as np
# sample data in a pandas dataframe
np.random.seed(1)
df=pd.DataFrame(dict(A=np.random.uniform(low=-1, high=2, size=25).tolist(),
B=np.random.uniform(low=-4, high=3, size=25).tolist(),
C=np.random.uniform(low=-1, high=3, size=25).tolist(),
))
df = df.cumsum()
# define colors as a list
colors = px.colors.qualitative.Plotly
# convert plotly hex colors to rgba to enable transparency adjustments
def hex_rgba(hex, transparency):
col_hex = hex.lstrip('#')
col_rgb = list(int(col_hex[i:i+2], 16) for i in (0, 2, 4))
col_rgb.extend([transparency])
areacol = tuple(col_rgb)
return areacol
rgba = [hex_rgba(c, transparency=0.2) for c in colors]
colCycle = ['rgba'+str(elem) for elem in rgba]
# Make sure the colors run in cycles if there are more lines than colors
def next_col(cols):
while True:
for col in cols:
yield col
line_color=next_col(cols=colCycle)
# plotly figure
fig = go.Figure()
# add line and shaded area for each series and standards deviation
for i, col in enumerate(df):
new_col = next(line_color)
x = list(df.index.values+1)
y1 = df[col]
y1_upper = [(y + np.std(df[col])) for y in df[col]]
y1_lower = [(y - np.std(df[col])) for y in df[col]]
y1_lower = y1_lower[::-1]
# standard deviation area
fig.add_traces(go.Scatter(x=x+x[::-1],
y=y1_upper+y1_lower,
fill='tozerox',
fillcolor=new_col,
line=dict(color='rgba(255,255,255,0)'),
showlegend=False,
name=col))
# line trace
fig.add_traces(go.Scatter(x=x,
y=y1,
line=dict(color=new_col, width=2.5),
mode='lines',
name=col)
)
# set x-axis
fig.update_layout(xaxis=dict(range=[1,len(df)]))
fig.show()
I was able to come up with something similar. I post the code here to be used by someone else or for any suggestions for improvements.
import matplotlib
import random
import plotly.graph_objects as go
import numpy as np
#random color generation in plotly
hex_colors_dic = {}
rgb_colors_dic = {}
hex_colors_only = []
for name, hex in matplotlib.colors.cnames.items():
hex_colors_only.append(hex)
hex_colors_dic[name] = hex
rgb_colors_dic[name] = matplotlib.colors.to_rgb(hex)
data = [[1, 3, 5, 4],
[2, 3, 5, 4],
[1, 1, 4, 5],
[2, 3, 5, 4]]
#calculating mean and standard deviation
mean=np.mean(data,axis=0)
std=np.std(data,axis=0)
#draw figure
fig = go.Figure()
c = random.choice(hex_colors_only)
fig.add_trace(go.Scatter(x=np.arange(4), y=mean+std,
mode='lines',
line=dict(color=c,width =0.1),
name='upper bound'))
fig.add_trace(go.Scatter(x=np.arange(4), y=mean,
mode='lines',
line=dict(color=c),
fill='tonexty',
name='mean'))
fig.add_trace(go.Scatter(x=np.arange(4), y=mean-std,
mode='lines',
line=dict(color=c, width =0.1),
fill='tonexty',
name='lower bound'))
fig.show()
Great custom responses posted by others. In case someone is interested in code from the official plotly website, see here: https://plotly.com/python/continuous-error-bars/
I wrote a function to extend plotly.express.line with the same high level interface of Plotly Express. The line function (source code below) is used in the same exact way as plotly.express.line but allows for continuous error bands with the flag argument error_y_mode which can be either 'band' or 'bar'. In the second case it produces the same result as the original plotly.express.line. Here is an usage example:
import plotly.express as px
df = px.data.gapminder().query('continent=="Americas"')
df = df[df['country'].isin({'Argentina','Brazil','Colombia'})]
df['lifeExp std'] = df['lifeExp']*.1 # Invent some error data...
for error_y_mode in {'band', 'bar'}:
fig = line(
data_frame = df,
x = 'year',
y = 'lifeExp',
error_y = 'lifeExp std',
error_y_mode = error_y_mode, # Here you say `band` or `bar`.
color = 'country',
title = f'Using error {error_y_mode}',
markers = '.',
)
fig.show()
which produces the following two plots:
The source code of the line function that extends plotly.express.line is this:
import plotly.express as px
import plotly.graph_objs as go
def line(error_y_mode=None, **kwargs):
"""Extension of `plotly.express.line` to use error bands."""
ERROR_MODES = {'bar','band','bars','bands',None}
if error_y_mode not in ERROR_MODES:
raise ValueError(f"'error_y_mode' must be one of {ERROR_MODES}, received {repr(error_y_mode)}.")
if error_y_mode in {'bar','bars',None}:
fig = px.line(**kwargs)
elif error_y_mode in {'band','bands'}:
if 'error_y' not in kwargs:
raise ValueError(f"If you provide argument 'error_y_mode' you must also provide 'error_y'.")
figure_with_error_bars = px.line(**kwargs)
fig = px.line(**{arg: val for arg,val in kwargs.items() if arg != 'error_y'})
for data in figure_with_error_bars.data:
x = list(data['x'])
y_upper = list(data['y'] + data['error_y']['array'])
y_lower = list(data['y'] - data['error_y']['array'] if data['error_y']['arrayminus'] is None else data['y'] - data['error_y']['arrayminus'])
color = f"rgba({tuple(int(data['line']['color'].lstrip('#')[i:i+2], 16) for i in (0, 2, 4))},.3)".replace('((','(').replace('),',',').replace(' ','')
fig.add_trace(
go.Scatter(
x = x+x[::-1],
y = y_upper+y_lower[::-1],
fill = 'toself',
fillcolor = color,
line = dict(
color = 'rgba(255,255,255,0)'
),
hoverinfo = "skip",
showlegend = False,
legendgroup = data['legendgroup'],
xaxis = data['xaxis'],
yaxis = data['yaxis'],
)
)
# Reorder data as said here: https://stackoverflow.com/a/66854398/8849755
reordered_data = []
for i in range(int(len(fig.data)/2)):
reordered_data.append(fig.data[i+int(len(fig.data)/2)])
reordered_data.append(fig.data[i])
fig.data = tuple(reordered_data)
return fig

Categories