Voici une solution qui utilise axvspan
pour dessiner plusieurs points saillants dont les limites sont fixées à l'aide des indices des données boursières correspondant aux pics et aux creux.
Les données sur les stocks contiennent généralement une variable temporelle discontinue où les week-ends et les jours fériés ne sont pas pris en compte. Leur représentation graphique dans matplotlib ou pandas produira des écarts le long de l'axe des x pour les week-ends et les jours fériés lorsqu'il s'agit de cours boursiers quotidiens. Ce phénomène peut ne pas être perceptible lorsque les plages de dates sont longues et/ou que les chiffres sont petits (comme dans cet exemple), mais il deviendra apparent si vous effectuez un zoom avant et c'est peut-être quelque chose que vous souhaitez éviter.
C'est pourquoi je partage ici un exemple complet qui présente des caractéristiques :
- Un ensemble de données réalistes comprenant un échantillon discontinu
DatetimeIndex
sur la base du calendrier des transactions de la Bourse de New York importé avec la pandas_market_calendars ainsi que de fausses données boursières qui ressemblent aux vraies.
- A pandas plot créé avec
use_index=False
qui supprime les écarts pour les week-ends et les jours fériés en utilisant à la place une plage d'entiers pour l'axe des abscisses. Le résultat obtenu ax
est utilisé de manière à éviter d'avoir à importer matplotlib.pyplot (à moins que vous n'ayez besoin de plt.show
).
- Une détection automatique des drawdowns sur l'ensemble de la plage de dates en utilisant le signal scipy.signal.
find_peaks
qui renvoie les indices nécessaires pour tracer les points saillants à l'aide de la fonction axvspan
. Le calcul des drawdowns d'une manière plus correcte nécessiterait une définition claire de ce qui est considéré comme un drawdown et conduirait à un code plus compliqué, ce qui est un sujet à traiter dans le cadre d'une étude de cas. autre question .
- Des ticks correctement formatés sont créés en parcourant en boucle les horodatages de l'élément
DatetimeIndex
étant donné que tous les matplotlib.dates les localisateurs et formateurs de tiques ainsi que les DatetimeIndex
des propriétés telles que .is_month_start
ne peut pas être utilisé dans ce cas.
Créer un échantillon de données
import numpy as np # v 1.19.2
import pandas as pd # v 1.1.3
import pandas_market_calendars as mcal # v 1.6.1
from scipy.signal import find_peaks # v 1.5.2
# Create datetime index with a 'trading day end' frequency based on the New York Stock
# Exchange trading hours (end date is inclusive)
nyse = mcal.get_calendar('NYSE')
nyse_schedule = nyse.schedule(start_date='2019-10-01', end_date='2021-02-01')
nyse_dti = mcal.date_range(nyse_schedule, frequency='1D').tz_convert(nyse.tz.zone)
# Create sample of random data for daily stock closing price
rng = np.random.default_rng(seed=1234) # random number generator
price = 100 + rng.normal(size=nyse_dti.size).cumsum()
df = pd.DataFrame(data=dict(price=price), index=nyse_dti)
df.head()
# price
# 2019-10-01 16:00:00-04:00 98.396163
# 2019-10-02 16:00:00-04:00 98.460263
# 2019-10-03 16:00:00-04:00 99.201154
# 2019-10-04 16:00:00-04:00 99.353774
# 2019-10-07 16:00:00-04:00 100.217517
Tracer les points saillants pour les tirages avec des ticks correctement formatés
# Plot stock price
ax = df['price'].plot(figsize=(10, 5), use_index=False, ylabel='Price')
ax.set_xlim(0, df.index.size-1)
ax.grid(axis='x', alpha=0.3)
# Highlight drawdowns using the indices of stock peaks and troughs: find peaks and
# troughs based on signal analysis rather than an algorithm for drawdowns to keep
# example simple. Width and prominence have been handpicked for this example to work.
peaks, _ = find_peaks(df['price'], width=7, prominence=4)
troughs, _ = find_peaks(-df['price'], width=7, prominence=4)
for peak, trough in zip(peaks, troughs):
ax.axvspan(peak, trough, facecolor='red', alpha=.2)
# Create and format monthly ticks
ticks = [idx for idx, timestamp in enumerate(df.index)
if (timestamp.month != df.index[idx-1].month) | (idx == 0)]
ax.set_xticks(ticks)
labels = [tick.strftime('%b\n%Y') if df.index[ticks[idx]].year
!= df.index[ticks[idx-1]].year else tick.strftime('%b')
for idx, tick in enumerate(df.index[ticks])]
ax.set_xticklabels(labels)
ax.figure.autofmt_xdate(rotation=0, ha='center')
ax.set_title('Drawdowns are highlighted in red', pad=15, size=14);
Dans un souci d'exhaustivité, il convient de noter que vous pouvez obtenir exactement le même résultat en utilisant la fonction fill_between
bien qu'il faille quelques lignes de code supplémentaires :
ax.set_ylim(*ax.get_ylim()) # remove top and bottom gaps with plot frame
drawdowns = np.repeat(False, df['price'].size)
for peak, trough in zip(peaks, troughs):
drawdowns[np.arange(peak, trough+1)] = True
ax.fill_between(np.arange(df.index.size), *ax.get_ylim(), where=drawdowns,
facecolor='red', alpha=.2)
Vous utilisez l'interface interactive de matplotlib et souhaitez avoir des ticks dynamiques lorsque vous zoomez ? Vous devrez alors utiliser les localisateurs et les formateurs de la base de données matplotlib.ticker module. Vous pouvez, par exemple, conserver les tics majeurs fixes comme dans cet exemple et ajouter des tics mineurs dynamiques pour afficher les jours ou les semaines de l'année lorsque vous zoomez. Vous trouverez un exemple de cette méthode à la fin de la section cette réponse .