Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Segment anchoring problem

Open
#689 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
15/100
Issue type
Bug
Clarity
Needs clarification
Activity status
Stale
Tech stack
matplotlib, pandas, python

Research direction

Start with the add_lines and update_chart entry points in the supplied script, then inspect how the chart axes are rebuilt during zoom and navigation. Reproduce the line movement with the attached CSV and determine the expected anchoring behavior; no repository file or test is identified in the issue.

Written by the indexing model from the issue text.

Description

question

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()
Dominant language
Python
Stars
4.4k
Forks
678
PR merge metrics
No merged PRs in 30d

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from matplotlib/mplfinance

All issues in matplotlib/mplfinance

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.