Skip to content

Commit 46dcc89

Browse files
Merge pull request #634 from DanielGoldfarb/master
Style Enhancements, and add styles from Chandrakant
2 parents 111a0a5 + b0294db commit 46dcc89

File tree

13 files changed

+407
-42
lines changed

13 files changed

+407
-42
lines changed

.github/workflows/mplfinance_checks.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ jobs:
55
runs-on: ubuntu-20.04
66
strategy:
77
matrix:
8-
python-version: [3.6, 3.7, 3.8, 3.9]
8+
python-version: ['3.8', '3.9', '3.10' ]
99
steps:
1010
- name: Preliminary Information
1111
run: |
@@ -27,7 +27,7 @@ jobs:
2727
- run: echo "The ${{ github.repository }} repository has been cloned to the runner."
2828

2929
- name: Set up Python ${{ matrix.python-version }}
30-
uses: actions/setup-python@v2
30+
uses: actions/setup-python@v4
3131
with:
3232
python-version: ${{ matrix.python-version }}
3333

examples/scratch_pad/issues/issue#241_loop_all_styles.ipynb

+186-14
Large diffs are not rendered by default.
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import yfinance as yf
2+
import mplfinance as mpf
3+
import matplotlib.pyplot as plt
4+
import matplotlib.patches as mpatches
5+
import pandas as pd
6+
import numpy as np
7+
8+
# Dates to get stock data
9+
start_date = "2020-01-01"
10+
end_date = "2023-06-15"
11+
12+
# Fetch Tesla stock data
13+
tesla_data = yf.download("TSLA", start=start_date, end=end_date)
14+
tesla_weekly_data = tesla_data.resample("W").agg(
15+
{"Open": "first", "High": "max", "Low": "min", "Close": "last", "Volume": "sum"}
16+
).dropna()
17+
18+
# Get the latest closing price
19+
latest_price = tesla_weekly_data['Close'][-1]
20+
21+
# Create additional plot
22+
close_price = tesla_weekly_data['Close']
23+
apd = mpf.make_addplot(close_price, color='cyan', width=2)
24+
25+
# Plot the candlestick chart
26+
fig, axes = mpf.plot(tesla_weekly_data,
27+
type='candle',
28+
addplot=apd,
29+
style='yahoo',
30+
title='Tesla Stock Prices',
31+
ylabel='Price',
32+
xlabel='Date',
33+
volume=True,
34+
ylabel_lower='Volume',
35+
volume_panel=1,
36+
figsize=(16, 8),
37+
returnfig=True
38+
)
39+
40+
# Move the y-axis labels to the left side
41+
axes[0].yaxis.tick_left()
42+
axes[1].yaxis.tick_left()
43+
44+
# Adjust the position of the y-axis label for price
45+
axes[0].yaxis.set_label_coords(-0.08, 0.5)
46+
47+
# Adjust the position of the y-axis label for volume
48+
axes[1].yaxis.set_label_coords(-0.08, 0.5)
49+
50+
# Set y-axis label for price and volume
51+
axes[0].set_ylabel('Price', rotation=0, labelpad=20)
52+
axes[1].set_ylabel('Volume', rotation=0, labelpad=20)
53+
54+
# Make the legend box
55+
handles = axes[0].get_legend_handles_labels()[0]
56+
red_patch = mpatches.Patch(color='red')
57+
green_patch = mpatches.Patch(color='green')
58+
cyan_patch = mpatches.Patch(color='cyan')
59+
handles = handles[:2] + [red_patch, green_patch, cyan_patch]
60+
labels = ["Price Up", "Price Down", "Closing Price"]
61+
axes[0].legend(handles=handles, labels=labels)
62+
63+
# Add a box to display the current price
64+
latest_price_text = f"Current Price: ${latest_price:.2f}"
65+
box_props = dict(boxstyle='round', facecolor='white', edgecolor='black', alpha=0.8)
66+
axes[0].text(0.02, 0.95, latest_price_text, transform=axes[0].transAxes,
67+
fontsize=12, verticalalignment='top', bbox=box_props)
68+
69+
# Function to create hover annotations
70+
def hover_annotations(data):
71+
72+
annot_visible = False
73+
annot = axes[0].text(0, 0, '', visible=False, ha='left', va='top')
74+
75+
def onmove(event):
76+
nonlocal annot_visible
77+
nonlocal annot
78+
79+
if event.inaxes == axes[0]:
80+
index = int(event.xdata)
81+
if index >= len(data.index):
82+
index = -1
83+
elif index < 0:
84+
index = 0
85+
values = data.iloc[index]
86+
mytext = (f"{values.name.date().strftime('%m/%d/%Y'):}\n"+
87+
f"O: {values['Open']:.2f}\n"+
88+
f"H: {values['High']:.2f}\n"+
89+
f"L: {values['Low']:.2f}\n"+
90+
f"C: {values['Close']:.2f}\n"+
91+
f"V: {values['Volume']:.0f}"
92+
)
93+
94+
annot_visible = True
95+
else:
96+
mytext = ''
97+
annot_visible = False
98+
99+
annot.set_position((event.xdata,event.ydata))
100+
annot.set_text(mytext)
101+
annot.set_visible(annot_visible)
102+
fig.canvas.draw_idle()
103+
104+
fig.canvas.mpl_connect('motion_notify_event', onmove)
105+
106+
return annot
107+
108+
109+
# Attach hover annotations to the plot
110+
annotations = hover_annotations(tesla_weekly_data)
111+
112+
# Display the chart
113+
plt.show()

src/mplfinance/_styledata/__init__.py

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
from mplfinance._styledata import binance
1717
from mplfinance._styledata import kenan
1818
from mplfinance._styledata import ibd
19+
from mplfinance._styledata import binancedark
20+
from mplfinance._styledata import tradingview
1921

2022
_style_names = [n for n in dir() if not n.startswith('_')]
2123

@@ -25,19 +27,29 @@
2527
eval(cmd)
2628

2729
def _validate_style(style):
30+
# Check for mandatory style keys:
2831
keys = ['base_mpl_style','marketcolors','mavcolors','y_on_right',
2932
'gridcolor','gridstyle','facecolor','rc' ]
3033
for key in keys:
3134
if key not in style.keys():
3235
err = f'Key "{key}" not found in style:\n\n {style}'
3336
raise ValueError(err)
3437

38+
# Check for mandatory marketcolor keys:
3539
mktckeys = ['candle','edge','wick','ohlc','volume','alpha']
3640
for key in mktckeys:
3741
if key not in style['marketcolors'].keys():
3842
err = f'Key "{key}" not found in marketcolors for style:\n\n {style}'
3943
raise ValueError(err)
4044

45+
# The following keys are not mandatory in the style file,
46+
# but maybe mandatory in the code (to keep the code simpler)
47+
# so we set default values here:
48+
if 'vcedge' not in style['marketcolors']:
49+
style['marketcolors']['vcedge'] = style['marketcolors']['volume']
50+
if 'vcdopcod' not in style['marketcolors']:
51+
style['marketcolors']['vcdopcod'] = False
52+
4153
#print('type(_styles)=',type(_styles))
4254
#print('_styles=',_styles)
4355
for s in _styles.keys():
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
style = dict(style_name = 'binancedark',
2+
base_mpl_style= 'dark_background',
3+
marketcolors = {'candle' : {'up': '#3dc985', 'down': '#ef4f60'},
4+
'edge' : {'up': '#3dc985', 'down': '#ef4f60'},
5+
'wick' : {'up': '#3dc985', 'down': '#ef4f60'},
6+
'ohlc' : {'up': 'green', 'down': 'red'},
7+
'volume' : {'up': '#247252', 'down': '#82333f'},
8+
'vcedge' : {'up': '#247252', 'down': '#82333f'},
9+
'vcdopcod' : False,
10+
'alpha' : 1.0,
11+
},
12+
mavcolors = ['#ffc201','#ff10ff','#cd0468','#1f77b4',
13+
'#ff7f0e','#2ca02c','#40e0d0'],
14+
y_on_right = True,
15+
gridcolor = None,
16+
gridstyle = '--',
17+
facecolor = None,
18+
rc = [ ('axes.grid','True'),
19+
('axes.grid.axis' , 'y'),
20+
('axes.edgecolor' , '#474d56' ),
21+
('axes.titlecolor','red'),
22+
('figure.titlesize', 'x-large' ),
23+
('figure.titleweight','semibold'),
24+
('figure.facecolor', '#0a0a0a' ),
25+
],
26+
base_mpf_style= 'binancedark'
27+
)

src/mplfinance/_styledata/brasil.py

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
'wick' : {'up': '#fedf00', 'down': '#002776'},
55
'ohlc' : {'up': '#fedf00', 'down': '#002776'},
66
'volume': {'up': '#fedf00', 'down': '#002776'},
7-
'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'},
87
'vcdopcod': False,
98
'alpha': 0.9},
109
'mavcolors' : None,

src/mplfinance/_styledata/checkers.py

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
'wick' : {'up': '#606060', 'down': '#606060'},
55
'ohlc' : {'up': '#000000', 'down': '#ff0000'},
66
'volume': {'up': '#6f6f6f', 'down': '#ff4040'},
7-
'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'},
87
'vcdopcod' : False,
98
'alpha' : 0.9},
109
'mavcolors' : None,

src/mplfinance/_styledata/starsandstripes.py

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
'wick' : {'up': '#082865', 'down': '#ae0019'},
55
'ohlc' : {'up': '#082865', 'down': '#ae0019'},
66
'volume': {'up': '#082865', 'down': '#ae0019'},
7-
'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'},
87
'vcdopcod': False,
98
'alpha': 0.9},
109
'mavcolors': None,
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
style = dict(style_name = 'tradingview',
2+
base_mpl_style= 'fast',
3+
marketcolors = {'candle' : {'up': '#26a69a', 'down': '#ef5350'},
4+
'edge' : {'up': '#26a69a', 'down': '#ef5350'},
5+
'wick' : {'up': '#26a69a', 'down': '#ef5350'},
6+
'ohlc' : {'up': '#26a69a', 'down': '#ef5350'},
7+
'volume' : {'up': '#26a69a', 'down': '#ef5350'},
8+
'vcedge' : {'up': 'white' , 'down': 'white' },
9+
'vcdopcod' : False,
10+
'alpha' : 1.0,
11+
'volume_alpha': 0.65,
12+
},
13+
#scale_width_adjustment = { 'volume': 0.8 },
14+
mavcolors = ['#2962ff','#2962ff',],
15+
y_on_right = True,
16+
gridcolor = None,
17+
gridstyle = '--',
18+
facecolor = None,
19+
rc = [ ('axes.grid','True'),
20+
('axes.edgecolor' , 'grey' ),
21+
('axes.titlecolor','red'),
22+
('figure.titlesize', 'x-large' ),
23+
('figure.titleweight','semibold'),
24+
('figure.facecolor', 'white' ),
25+
],
26+
base_mpf_style = 'tradingview'
27+
)

src/mplfinance/_styledata/yahoo.py

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
'wick' : {'up': '#606060', 'down': '#606060'},
55
'ohlc' : {'up': '#00b060', 'down': '#fe3032'},
66
'volume': {'up': '#4dc790', 'down': '#fd6b6c'},
7-
'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'},
87
'vcdopcod' : True,
98
'alpha' : 0.9},
109
'mavcolors' : None,

src/mplfinance/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version_info = (0, 12, 9, 'beta', 8)
1+
version_info = (0, 12, 9, 'beta', 9)
22

33
_specifier_ = {'alpha': 'a','beta': 'b','candidate': 'rc','final': ''}
44

src/mplfinance/_widths.py

+20-15
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,21 @@ def _valid_update_width_kwargs():
102102

103103
return vkwargs
104104

105+
def _scale_width_config(scale,width_config):
106+
if scale['volume'] is not None:
107+
width_config['volume_width'] *= scale['volume']
108+
if scale['ohlc'] is not None:
109+
width_config['ohlc_ticksize'] *= scale['ohlc']
110+
if scale['candle'] is not None:
111+
width_config['candle_width'] *= scale['candle']
112+
if scale['lines'] is not None:
113+
width_config['line_width'] *= scale['lines']
114+
if scale['volume_linewidth'] is not None:
115+
width_config['volume_linewidth'] *= scale['volume_linewidth']
116+
if scale['ohlc_linewidth'] is not None:
117+
width_config['ohlc_linewidth' ] *= scale['ohlc_linewidth']
118+
if scale['candle_linewidth'] is not None:
119+
width_config['candle_linewidth'] *= scale['candle_linewidth']
105120

106121
def _determine_width_config( xdates, config ):
107122
'''
@@ -138,23 +153,13 @@ def _determine_width_config( xdates, config ):
138153
width_config['candle_linewidth'] = _dfinterpolate(_widths,datalen,'clw')
139154
width_config['line_width' ] = _dfinterpolate(_widths,datalen,'lw')
140155

141-
if config['scale_width_adjustment'] is not None:
156+
if 'scale_width_adjustment' in config['style']:
157+
scale = _process_kwargs(config['style']['scale_width_adjustment'],_valid_scale_width_kwargs())
158+
_scale_width_config(scale,width_config)
142159

160+
if config['scale_width_adjustment'] is not None:
143161
scale = _process_kwargs(config['scale_width_adjustment'],_valid_scale_width_kwargs())
144-
if scale['volume'] is not None:
145-
width_config['volume_width'] *= scale['volume']
146-
if scale['ohlc'] is not None:
147-
width_config['ohlc_ticksize'] *= scale['ohlc']
148-
if scale['candle'] is not None:
149-
width_config['candle_width'] *= scale['candle']
150-
if scale['lines'] is not None:
151-
width_config['line_width'] *= scale['lines']
152-
if scale['volume_linewidth'] is not None:
153-
width_config['volume_linewidth'] *= scale['volume_linewidth']
154-
if scale['ohlc_linewidth'] is not None:
155-
width_config['ohlc_linewidth' ] *= scale['ohlc_linewidth']
156-
if scale['candle_linewidth'] is not None:
157-
width_config['candle_linewidth'] *= scale['candle_linewidth']
162+
_scale_width_config(scale,width_config)
158163

159164
if config['update_width_config'] is not None:
160165

src/mplfinance/plotting.py

+19-6
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ def _valid_plot_kwargs():
383383
'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2
384384
and all([isinstance(v,(int,float)) for v in value])},
385385

386-
'volume_alpha' : { 'Default' : 1, # alpha of Volume bars
386+
'volume_alpha' : { 'Default' : None, # alpha of Volume bars
387387
'Description' : 'opacity for Volume bar: 0.0 (transparent) to 1.0 (opaque)',
388388
'Validator' : lambda value: isinstance(value,(int,float)) or
389389
all([isinstance(v,(int,float)) for v in value]) },
@@ -673,7 +673,8 @@ def plot( data, **kwargs ):
673673

674674
datalen = len(xdates)
675675
if config['volume']:
676-
vup,vdown = style['marketcolors']['volume'].values()
676+
mc = style['marketcolors']
677+
vup,vdown = mc['volume'].values()
677678
#-- print('vup,vdown=',vup,vdown)
678679
vcolors = _updown_colors(vup, vdown, opens, closes, use_prev_close=style['marketcolors']['vcdopcod'])
679680
#-- print('len(vcolors),len(opens),len(closes)=',len(vcolors),len(opens),len(closes))
@@ -682,9 +683,21 @@ def plot( data, **kwargs ):
682683
w = config['_width_config']['volume_width']
683684
lw = config['_width_config']['volume_linewidth']
684685

685-
adjc = _adjust_color_brightness(vcolors,0.90)
686-
valp = config['volume_alpha']
687-
volumeAxes.bar(xdates,volumes,width=w,linewidth=lw,color=vcolors,ec=adjc,alpha=valp)
686+
veup, vedown = mc['vcedge'].values()
687+
if mc['volume'] == mc['vcedge']:
688+
edgecolors = _adjust_color_brightness(vcolors,0.90)
689+
elif veup != vedown:
690+
edgecolors = _updown_colors(veup, vedown, opens, closes, use_prev_close=style['marketcolors']['vcdopcod'])
691+
else:
692+
edgecolors = veup
693+
694+
if config['volume_alpha']:
695+
valp = config['volume_alpha']
696+
elif 'volume_alpha' in mc:
697+
valp = mc['volume_alpha']
698+
else:
699+
valp = 1.0
700+
volumeAxes.bar(xdates,volumes,width=w,linewidth=lw,color=vcolors,ec=edgecolors,alpha=valp)
688701
if config['volume_ylim'] is not None:
689702
vymin = config['volume_ylim'][0]
690703
vymax = config['volume_ylim'][1]
@@ -911,7 +924,7 @@ def plot( data, **kwargs ):
911924
else:
912925
title = config['title'] # config['title'] is a string
913926
fig.suptitle(title,**title_kwargs)
914-
927+
915928

916929
if config['axtitle'] is not None:
917930
axA1.set_title(config['axtitle'])

0 commit comments

Comments
 (0)