matplotlib/mplfinance

Segment anchoring problem

Opened this issue · 0 comments

Hello,
FX_NAS100, 5.csv

I hope I'm not out of line with the problem I can't solve.
I would like to draw a red line and a green line representing the amplitude of the US trading session on the NASDAQ100.
The history is in candlestick units of 5 minutes.
The two lines (for the historical period) should be drawn from the 15:30 candle to the 21:55 candle.
Tracing is correct at first, but the lines move as soon as you use the program's zoom or advance functions.
Perhaps tracing with “axhline” isn't the right solution; I've tried using the xplot method to exploit the position of the reference candle index, but I can't get anywhere.
Do you have any ideas for solving this problem?
Later, I want to draw other lines with different levels, but until I solve this problem, I'm stuck.

Thanks

import` pandas as pd
import mplfinance as mpf
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import ttk
import os
import matplotlib.pyplot as plt

df_bourse = pd.DataFrame([
    {'Année': 2022, 'Période': 'Été US', 'Début': '2022-03-14', 'Fin': '2022-03-28', 'Modedate': 1},
    {'Année': 2022, 'Période': 'Hiver US', 'Début': '2022-10-31', 'Fin': '2022-11-04', 'Modedate': 1},
    {'Année': 2023, 'Période': 'Été US', 'Début': '2023-03-13', 'Fin': '2023-03-24', 'Modedate': 1},
    {'Année': 2023, 'Période': 'Hiver US', 'Début': '2023-10-30', 'Fin': '2023-11-03', 'Modedate': 1},
    {'Année': 2024, 'Période': 'Été US', 'Début': '2024-03-11', 'Fin': '2024-03-28', 'Modedate': 1},
    {'Année': 2024, 'Période': 'Hiver US', 'Début': '2024-10-28', 'Fin': '2024-11-01', 'Modedate': 1}
])

df_bourse['Début'] = pd.to_datetime(df_bourse['Début'])
df_bourse['Fin'] = pd.to_datetime(df_bourse['Fin'])

csv_path = r"C:\Formation Python\FX_NAS100, 5.csv"
df = pd.read_csv(csv_path)
csv_filename = os.path.basename(csv_path)

df["time"] = pd.to_datetime(df["time"], unit='s')
df['date'] = df['time'].dt.date

def determine_modedate(date):
    for _, row in df_bourse.iterrows():
        if row['Début'].date() <= date <= row['Fin'].date():
            return row['Modedate']
    return 2

df['Modedate'] = df['date'].apply(determine_modedate)

df['amp_journée'] = 0.0
for day in df['date'].unique():
    day_data = df[df['date'] == day]
    if not day_data.empty:
        modedate = day_data['Modedate'].iloc[0]
        if modedate == 1:
            start_time = pd.to_datetime("14:30").time()
            end_time = pd.to_datetime("21:00").time()
        else:
            start_time = pd.to_datetime("15:30").time()
            end_time = pd.to_datetime("22:00").time()
        
        filtered_data = day_data[(day_data['time'].dt.time >= start_time) & (day_data['time'].dt.time <= end_time)]
        
        if not filtered_data.empty:
            high_max = filtered_data['high'].max()
            low_min = filtered_data['low'].min()
            amplitude = high_max - low_min
            df.loc[df['date'] == day, 'amp_journée'] = amplitude

percentages = [25, 50, 75, 100, 125, 150, 175, 200, 225, 250, 275]
for p in percentages:
    df[f'{p}%'] = df['amp_journée'] * (p / 100)
    df[f'-{p}%'] = df['amp_journée'] * (-p / 100)

cols = ['date', 'time', 'open', 'high', 'low', 'close', 'Modedate', 'amp_journée']
percentage_cols = [f'{p}%' for p in percentages] + [f'-{p}%' for p in percentages]
df = df[cols + percentage_cols]

# Interface graphique principale
root = tk.Tk()
root.title("Graphique en chandelier japonais")
root.state('zoomed')

frame = ttk.Frame(root)
frame.pack(fill=tk.BOTH, expand=True)

start_index = 0
num_candles = 100

# Champs pour les pas
step_frame = ttk.Frame(root)
step_frame.pack(side=tk.TOP, fill=tk.X)

tk.Label(step_frame, text="Pas graphique:").pack(side=tk.LEFT, padx=5)
graph_step_entry = ttk.Entry(step_frame, width=5)
graph_step_entry.insert(0, "30")
graph_step_entry.pack(side=tk.LEFT, padx=5)

tk.Label(step_frame, text="Pas zoom:").pack(side=tk.LEFT, padx=5)
zoom_step_entry = ttk.Entry(step_frame, width=5)
zoom_step_entry.insert(0, "30")
zoom_step_entry.pack(side=tk.LEFT, padx=5)

# Fenêtre pour afficher le DataFrame
def show_dataframe():
    df_window = tk.Toplevel(root)
    df_window.title("Tableau des données")
    df_window.state('zoomed')

    tree_frame = ttk.Frame(df_window)
    tree_frame.pack(fill=tk.BOTH, expand=True)

    tree_scroll_y = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL)
    tree_scroll_x = ttk.Scrollbar(tree_frame, orient=tk.HORIZONTAL)
    tree = ttk.Treeview(tree_frame, yscrollcommand=tree_scroll_y.set, xscrollcommand=tree_scroll_x.set)

    tree_scroll_y.pack(side=tk.RIGHT, fill=tk.Y)
    tree_scroll_x.pack(side=tk.BOTTOM, fill=tk.X)
    tree_scroll_y.config(command=tree.yview)
    tree_scroll_x.config(command=tree.xview)

    tree.pack(fill=tk.BOTH, expand=True)

    df_display = df.copy().reset_index()
    numeric_columns = ['amp_journée'] + [col for col in df_display.columns if '%' in col] + ['open', 'high', 'low', 'close']
    for column in numeric_columns:
        if column in df_display.columns:
            df_display[column] = df_display[column].round(2)

    tree["columns"] = ["index"] + list(df_display.columns)[1:]
    tree["show"] = "headings"

    tree.heading("index", text="Index")
    tree.column("index", width=50, anchor='center')

    for column in df_display.columns[1:]:
        tree.heading(column, text=column)
        if column == 'time':
            tree.column(column, width=200, anchor='center')
        else:
            tree.column(column, width=100, anchor='center')

    for _, row in df_display.iterrows():
        tree.insert("", "end", values=[row['index']] + list(row)[1:])

    # Champ de saisie pour l'index
    index_frame = ttk.Frame(df_window)
    index_frame.pack(side=tk.TOP, fill=tk.X)
    tk.Label(index_frame, text="Index:").pack(side=tk.LEFT, padx=5)
    index_entry = ttk.Entry(index_frame, width=10)
    index_entry.pack(side=tk.LEFT, padx=5)

    def select_by_index(event=None):
        index = index_entry.get()
        if index.isdigit():
            index = int(index)
            for item in tree.get_children():
                if int(tree.item(item)['values'][0]) == index:
                    tree.selection_set(item)
                    tree.see(item)
                    break

    index_entry.bind('<Return>', select_by_index)

    df_window.mainloop()

btn_show_df = ttk.Button(root, text="Afficher le tableau", command=show_dataframe)
btn_show_df.pack(side=tk.TOP, pady=5)

# Ajouter une ligne rouge et verte
def add_lines(ax, df_slice):
    try:
        unique_dates = df_slice['date'].unique()
        for date in unique_dates:
            daily_data = df_slice[df_slice['date'] == date]
            if daily_data.empty:
                continue
            
            modedate = daily_data['Modedate'].iloc[0]
            if modedate == 1:
                start_time = pd.to_datetime("14:30").time()
                end_time = pd.to_datetime("21:00").time()
            else:
                start_time = pd.to_datetime("15:30").time()
                end_time = pd.to_datetime("21:55").time()
            
            filtered_data = daily_data[
                (daily_data['time'].dt.time >= start_time) & 
                (daily_data['time'].dt.time <= end_time)
            ]
            
            if filtered_data.empty:
                continue
            
            max_high = filtered_data['high'].max()
            min_low = filtered_data['low'].min()
            
            time_range = df_slice['time']
            xmin = (pd.Timestamp.combine(date, start_time) - time_range.min()).total_seconds() / (time_range.max() - time_range.min()).total_seconds()
            xmax = (pd.Timestamp.combine(date, end_time) - time_range.min()).total_seconds() / (time_range.max() - time_range.min()).total_seconds()
            
            ax.axhline(y=max_high, color='red', linestyle='--', xmin=xmin, xmax=xmax)
            ax.axhline(y=min_low, color='green', linestyle='--', xmin=xmin, xmax=xmax)
    except Exception as e:
        pass

# fonction pour gérer le mouvement de la souris
def on_mouse_move(event):
    if event.inaxes:
        x, y = event.xdata, event.ydata
        ax = event.inaxes
        
        # Effacer les lignes précédentes
        for line in ax.lines:
            if line.get_label() in ['crosshair_h', 'crosshair_v']:
                line.remove()
        
        # Dessiner les nouvelles lignes
        ax.axhline(y=y, color='black', linewidth=0.5, label='crosshair_h')
        ax.axvline(x=x, color='black', linewidth=0.5, label='crosshair_v')
        
        # Mettre à jour les données dans la fenêtre
        if df_slice is not None and len(df_slice) > 0:
            index = max(0, min(int(x), len(df_slice) - 1))
            data = df_slice.iloc[index]
            info_text = f"Index: {start_index + index}\nOpen: {data['open']:.2f}\nHigh: {data['high']:.2f}\nLow: {data['low']:.2f}\nClose: {data['close']:.2f}\nTime: {data['time']}"
            info_window.set(info_text)
            
            # Mise à jour des valeurs X et Y
            x_value.set(f"X: {data['time']}")
            y_value.set(f"Y: {y:.2f}")
        
        # Redessiner le graphique
        fig.canvas.draw_idle()

# Evènement molette de la sourie
def on_scroll(event):
    global num_candles
    if event.button == 'up':
        num_candles = max(num_candles - int(zoom_step_entry.get()), 10)
    elif event.button == 'down':
        num_candles += int(zoom_step_entry.get())
    update_chart()

# Fonctions pour le graphique
def update_chart():
    global start_index, num_candles, df_slice, fig
    plt.close('all')  # Ferme toutes les figures existantes
    df_slice = df.iloc[start_index:start_index + num_candles]
    if df_slice.empty:
        return
    df_ohlc = df_slice[['time', 'open', 'high', 'low', 'close']].copy()
    df_ohlc.set_index('time', inplace=True)
    fig, axlist = mpf.plot(df_ohlc, type='candle', style='charles', title=csv_filename,
                           ylabel='Prix', volume=False, returnfig=True)
    ax = axlist[0]
    add_lines(ax, df_slice)
    if hasattr(frame, "canvas"):
        frame.canvas.get_tk_widget().destroy()
    canvas = FigureCanvasTkAgg(fig, master=frame)
    canvas.draw()
    canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
    frame.canvas = canvas
    fig.canvas.mpl_connect('motion_notify_event', on_mouse_move)
    fig.canvas.mpl_connect('scroll_event', on_scroll)

def increase_candles():
    global num_candles
    num_candles += int(zoom_step_entry.get())
    update_chart()

def decrease_candles():
    global num_candles
    num_candles = max(num_candles - int(zoom_step_entry.get()), 10)  # Minimum de 10 chandeliers
    update_chart()

def move_right():
    global start_index
    start_index += int(graph_step_entry.get())
    update_chart()

def move_left():
    global start_index
    start_index = max(start_index - int(graph_step_entry.get()), 0)
    update_chart()

# Boutons
button_frame = ttk.Frame(root)
button_frame.pack(side=tk.BOTTOM, fill=tk.X)

plus_button = ttk.Button(button_frame, text="+", command=decrease_candles)
plus_button.pack(side=tk.LEFT, padx=5, pady=5)

minus_button = ttk.Button(button_frame, text="-", command=increase_candles)
minus_button.pack(side=tk.LEFT, padx=5, pady=5)

left_button = ttk.Button(button_frame, text="←", command=move_left)
left_button.pack(side=tk.LEFT, padx=5, pady=5)

right_button = ttk.Button(button_frame, text="→", command=move_right)
right_button.pack(side=tk.LEFT, padx=5, pady=5)

# Fenêtre d'information
info_frame = ttk.Frame(root)
info_frame.pack(side=tk.BOTTOM, fill=tk.X)

# Sous-frame pour les informations centrées
center_info_frame = ttk.Frame(info_frame)
center_info_frame.pack(side=tk.LEFT, expand=True)

info_window = tk.StringVar()
info_label = ttk.Label(center_info_frame, textvariable=info_window, justify=tk.LEFT)
info_label.pack(pady=10)

# Sous-frame pour les valeurs X et Y à droite
xy_info_frame = ttk.Frame(info_frame)
xy_info_frame.pack(side=tk.RIGHT)

x_value = tk.StringVar()
y_value = tk.StringVar()
x_label = ttk.Label(xy_info_frame, textvariable=x_value, justify=tk.LEFT)
y_label = ttk.Label(xy_info_frame, textvariable=y_value, justify=tk.LEFT)
x_label.pack(padx=10, anchor='w')
y_label.pack(padx=10, anchor='w')

update_chart()
root.mainloop()