Skip to content

Commit 3505283

Browse files
Merge branch 'master' of github.com:matplotlib/mplfinance
2 parents e3fca19 + 7b1b722 commit 3505283

File tree

2 files changed

+111
-5
lines changed

2 files changed

+111
-5
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'''
2+
This file contains a animation demo using mplfinance demonstating resampling
3+
of the data and re-displaying the most recent, partially resampled, candle.
4+
5+
The idea for this example came from Issue #256
6+
(https://github.com/matplotlib/mplfinance/issues/256)
7+
8+
The typical use-case is where the user has real-time data from an API,
9+
perhaps to the second, or minute, but wants to aggregate that data to
10+
15 minutes per canlde, or maybe 30 minutes per candle. At the same time,
11+
during those 15 or 30 minutes, the user wants to see the most recent
12+
candle changing and developing as real-time data continues to come in.
13+
14+
In the example presented in this file, the data is once per minute,
15+
with an aggregation of 15 minutes per candle. But, for this *simulation*
16+
we set the animation rate to 250ms, which means we are getting 1 minute's
17+
worth of data from the API every 1/4 second. Thus, this simulation is
18+
running 240 times faster than real-time.
19+
20+
In a real-life case, if we have data once per second, and want to aggregate
21+
15 minutes per candle, we would set the animation interval to something
22+
like 5000ms (once every 5 seconds) because a more frequent visualization
23+
might be impractical to watch or to use for decision making.
24+
25+
PLEASE NOTE: In this example, we resample the *entire* data set with each
26+
animation cycle. This is inefficient, but works fine for less than 5000
27+
data points or so. For larger data sets it may be practical to cache
28+
the resampled data up to the last "full" candle, and only resample the
29+
data that contributes to the final candle (and append it to the cached
30+
resampled data). If I have time, I will work up and example doing that.
31+
32+
NOTE: Presently mplfinance does not support "blitting" (blitting makes animation
33+
more efficient). Nonetheless, the animation is efficient enough to update at least
34+
once per second, and typically more frequently depending on the size of the plot.
35+
'''
36+
import pandas as pd
37+
import mplfinance as mpf
38+
import matplotlib.animation as animation
39+
40+
## Class to simulate getting more data from API:
41+
42+
class RealTimeAPI():
43+
def __init__(self):
44+
self.data_pointer = 0
45+
self.data_frame = pd.read_csv('data/SP500_NOV2019_IDay.csv',index_col=0,parse_dates=True)
46+
#self.data_frame = self.data_frame.iloc[0:120,:]
47+
self.df_len = len(self.data_frame)
48+
49+
def fetch_next(self):
50+
r1 = self.data_pointer
51+
self.data_pointer += 1
52+
if self.data_pointer >= self.df_len:
53+
return None
54+
return self.data_frame.iloc[r1:self.data_pointer,:]
55+
56+
def initial_fetch(self):
57+
if self.data_pointer > 0:
58+
return
59+
r1 = self.data_pointer
60+
self.data_pointer += int(0.2*self.df_len)
61+
return self.data_frame.iloc[r1:self.data_pointer,:]
62+
63+
rtapi = RealTimeAPI()
64+
65+
resample_map ={'Open' :'first',
66+
'High' :'max' ,
67+
'Low' :'min' ,
68+
'Close':'last' }
69+
resample_period = '15T'
70+
71+
df = rtapi.initial_fetch()
72+
rs = df.resample(resample_period).agg(resample_map).dropna()
73+
74+
fig, axes = mpf.plot(rs,returnfig=True,figsize=(11,8),type='candle',title='\n\nGrowing Candle')
75+
ax = axes[0]
76+
77+
def animate(ival):
78+
global df
79+
global rs
80+
nxt = rtapi.fetch_next()
81+
if nxt is None:
82+
print('no more data to plot')
83+
ani.event_source.interval *= 3
84+
if ani.event_source.interval > 12000:
85+
exit()
86+
return
87+
df = df.append(nxt)
88+
rs = df.resample(resample_period).agg(resample_map).dropna()
89+
ax.clear()
90+
mpf.plot(rs,ax=ax,type='candle')
91+
92+
ani = animation.FuncAnimation(fig, animate, interval=250)
93+
94+
mpf.show()

src/mplfinance/plotting.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def _valid_plot_kwargs():
148148
'Validator' : lambda value: mcolors.is_color_like(value) },
149149

150150
'title' : { 'Default' : None, # Figure Title
151-
'Validator' : lambda value: isinstance(value,str) },
151+
'Validator' : lambda value: isinstance(value,(str,dict)) },
152152

153153
'axtitle' : { 'Default' : None, # Axes Title (subplot title)
154154
'Validator' : lambda value: isinstance(value,str) },
@@ -624,16 +624,28 @@ def plot( data, **kwargs ):
624624
if external_axes_mode:
625625
volumeAxes.tick_params(axis='x',rotation=xrotation)
626626
volumeAxes.xaxis.set_major_formatter(formatter)
627-
627+
628628
if config['title'] is not None:
629629
if config['tight_layout']:
630630
# IMPORTANT: 0.89 is based on the top of the top panel
631631
# being at 0.18+0.7 = 0.88. See _panels.py
632632
# If the value changes there, then it needs to change here.
633-
fig.suptitle(config['title'],size='x-large',weight='semibold', va='bottom', y=0.89)
633+
title_kwargs = dict(size='x-large',weight='semibold', va='bottom', y=0.89)
634634
else:
635-
fig.suptitle(config['title'],size='x-large',weight='semibold', va='center')
636-
635+
title_kwargs = dict(size='x-large',weight='semibold', va='center')
636+
if isinstance(config['title'],dict):
637+
title_dict = config['title']
638+
if 'title' not in title_dict:
639+
raise ValueError('Must have "title" entry in title dict')
640+
else:
641+
title = title_dict['title']
642+
del title_dict['title']
643+
title_kwargs.update(title_dict) # allows override default values set by mplfinance above
644+
else:
645+
title = config['title'] # config['title'] is a string
646+
fig.suptitle(title,**title_kwargs)
647+
648+
637649
if config['axtitle'] is not None:
638650
axA1.set_title(config['axtitle'])
639651

0 commit comments

Comments
 (0)