1

I am trying to create a candlestick chart using finplot and embedding it in a gui using pyqt6. I have multiple csv files in a folder with the following columns: dt, open, high, low, close, and volume. Each csv file is named with the stock symbol(for e.g. TESLA.csv). I have added a functionality to my code where you can easily navigate through the files using a forward and back button. Anyways, here's my code:

import finplot as fplt
from functools import lru_cache
from PyQt6.QtWidgets import QApplication, QGridLayout, QGraphicsView, QComboBox, QLabel, QPushButton, QHBoxLayout, QWidget
from threading import Thread
import pandas as pd
import os

app = QApplication([])
win = QGraphicsView()
win.setWindowTitle('TradingView wannabe')
layout = QGridLayout()
win.setLayout(layout)
win.resize(800, 600)

data_folder = r"C:\Users\PC\Desktop\trade\pse\main\indv_stocks"  # Replace this with the actual path to your data folder

# Get the list of filenames (symbols) without the ".csv" extension in the folder
csv_files = [file[:-4] for file in os.listdir(data_folder) if file.endswith('.csv')]

# Create a widget to contain the combo box and navigation buttons horizontally
widget = QWidget()
widget_layout = QHBoxLayout()
widget.setLayout(widget_layout)

combo = QComboBox()
combo.setEditable(True)
combo.setFixedWidth(150)  # Set a fixed width for the combo box
[combo.addItem(i) for i in csv_files]  # Use the list of filenames as items in the combo box
widget_layout.addWidget(combo)

info = QLabel()
widget_layout.addWidget(info)

# Create forward and backward navigation buttons
button_forward = QPushButton("➡")
button_backward = QPushButton("⬅")
button_forward.setFixedSize(30, 30)  # Set a fixed size for the buttons
button_backward.setFixedSize(30, 30)
widget_layout.addWidget(button_backward)
widget_layout.addWidget(button_forward)

# Add the widget containing the combo box and buttons to the layout
layout.addWidget(widget, 0, 0, 1, 2)

ax = fplt.create_plot(init_zoom_periods=100)
win.axs = [ax]  # finplot requires this property
axo = ax.overlay()
layout.addWidget(ax.vb.win, 1, 0, 1, 2)


@lru_cache(maxsize=15)
def download(symbol):
    file_path = os.path.join(data_folder, f"{symbol}.csv")
    if not os.path.exists(file_path):
        return None
    return pd.read_csv(file_path, index_col=0, parse_dates=True)

@lru_cache(maxsize=100)
def get_name(symbol):
    return symbol  # If you don't have a CSV with symbol names, you can return the symbol itself.

current_index = 0
def update(txt):
    global current_index
    df = download(txt)
    if df is None or len(df) < 20:  # Symbol does not exist or has insufficient data
        return
    info.setText('Loading symbol name...')
    price = df['open close high low'.split()]
    ema20 = df.close.ewm(span = 20, adjust = False).mean()
    ema50 = df.close.ewm(span = 50, adjust = False).mean()
    ema200 = df.close.ewm(span = 200, adjust = False).mean()
    volume = df['open close volume'.split()]
    ax.reset()  # remove previous plots
    axo.reset()  # remove previous plots
    fplt.candlestick_ochl(price)
    fplt.plot(ema20, legend='EMA20')
    fplt.plot(ema50, legend='EMA50')
    fplt.plot(ema200, legend='EMA200')
    fplt.volume_ocv(volume, ax=axo)
    fplt.refresh()  # refresh autoscaling when all plots complete
    Thread(target=lambda: info.setText(get_name(txt))).start()  # slow, so use thread

    # Update the current index when the combo box selection changes
    global csv_files
    current_index = csv_files.index(txt)

# Connect the currentTextChanged signal of the combo box to the update function
combo.currentTextChanged.connect(update)

# Function to handle the button clicks for navigation
def navigate_forward():
    global current_index
    if current_index < len(csv_files) - 1:
        current_index += 1
        combo.setCurrentIndex(current_index)

def navigate_backward():
    global current_index
    if current_index > 0:
        current_index -= 1
        combo.setCurrentIndex(current_index)

# Connect the button clicks to the navigation functions
button_forward.clicked.connect(navigate_forward)
button_backward.clicked.connect(navigate_backward)

update(combo.currentText())

fplt.show(qt_exec=False)  # prepares plots when they're all setup
win.show()
app.exec()

This works but what I also want to achieve is to display in a legend the value of the open, high, low, close, volume in the top of the chart (beside the combobox) when the mouse hovers over a date. Not allowed to post image yet to show what i want but if you go to tradingview.com you’ll see an example like this: O 12.50 H 15 L 11 C 13 V 1,000,000

I was able to do it but without embedding it in a gui. Tried chatgpt too but all the answers that it spewed produced errors

moj
  • 11
  • 2

1 Answers1

0

You need to use fplt.set_time_inspector, take a look at snp500.py example, it does exactly that.

def update_legend_text(x, y):
    row = df.loc[df.Date==x]
    # format html with the candle and set legend
    fmt = '<span style="color:#%s">%%.2f</span>' % ('0b0' if (row.Open<row.Close).all() else 'a00')
    rawtxt = '<span style="font-size:13px">%%s %%s</span> &nbsp; O%s C%s H%s L%s' % (fmt, fmt, fmt, fmt)
    values = [v.iloc[0] for v in (row.Open, row.Close, row.High, row.Low)]
    hover_label.setText(rawtxt % tuple([symbol, interval.upper()] + values))

fplt.set_time_inspector(update_legend_text, ax=ax, when='hover')
mugiseyebrows
  • 4,138
  • 1
  • 14
  • 15