PySide6 打造 TradingView级专业K线图,超简单教程来袭!

1,147 阅读4分钟

TradingView 是一款广受交易者青睐的行情分析软件,而 lightweight-charts 是其推出的精简版 Js 开源库。凭借 TradingView 在交易领域的深厚积累,lightweight-charts 拥有高性能与出色的用户体验。

lightweight-charts-python 是基于 TradingView 的 lightweight-charts 开发的 Python 版本。它通过 webview 将 lightweight-charts 集成到 Python 环境中,让 Python 开发者无需掌握 Javascript,即可轻松使用 lightweight-charts。

此外,Python 的灵活性使得 lightweight-charts-python 能与各类框架无缝对接。例如,国内知名的实盘框架 vnpy,其 GUI 界面基于 PySide6 构建。lightweight-charts-python 可轻松集成到 PySide6 中,进而融入我们的CryptoTrader项目中,为大家提供更强大的工具。

1. 环境准备

1.1 安装lightweight-charts-python

pip install lightweight-charts

1.2 导入对应的库

from datetime import datetime  
import pandas as pd  
  
from gui.ui import QtCore  
from gui.ui.qt import QVBoxLayoutQMainWindowQWidgetQApplication  
from core.trader.constant import ExchangeInterval  
from core.trader.database import get_database, BaseDatabase  
  
from lightweight_charts.widgets import QtChart
  • • gui.ui封装了pyside6的类;
  • • core.trader.constant里面封装了常数项;
  • • core.trader.database封装了数据库的实现。

1.3 获取数据

database: BaseDatabase = get_database()  
bars = database.load_bar_data(  
    symbol="BTC-USDT-SWAP",  
    exchange=Exchange.OKX,  
    interval=Interval.MINUTE,  
    start=datetime(202437),  
    end=datetime(202491)  
)  

n = 1000  
history = bars[:n]  
new_data = bars[n:]  
  
data: list = []  
for bar in history:  
    data.append([bar.datetime.strftime("%Y-%m-%d %H:%M:%S"), bar.open_price, bar.high_price, bar.low_price, bar.close_price, bar.volume])  
  
df = pd.DataFrame(data, columns=["time""open""high""low""close""volume"]) 
  • • 取从2024年3月7日到2024年9月1日OKX的"BTC-USDT-SWAP"一分钟数据;
  • • 取前1000根K线数据进行初始化,后面的数据动态展示的时候使用;
  • • 讲从数据库获取的时候转化为DataFrame格式数据。

2. 功能展示

2.1 展示历史数据

widget = QtChart(main_widget)  
widget.watermark(text='知识星球:创新技术阁')
widget.set(df)
  • • 创建一个QtChart,用于在pyside6中主窗口使用;
  • • 添加水印;
  • • 设置数据。

Pasted image 20250423185927.png

2.2 增加指标

定义主图SAM计算方法:

def calculate_sma(df, period:int=20):  
    return pd.DataFrame({  
        'time': df['time'],  
        f'SMA{period}': df['close'].rolling(window=period).mean()  
    }).dropna()

定义副图RSI的计算方法:

def calculate_rsi(df, period:int=14):  
    return pd.DataFrame({  
        'time'df['time'],  
        f'RSI': talib.RSI(df['close'], timeperiod=14)  
    })

分别添加指标SAM20和RSI到主图和副图:

line = widget.create_line('SMA20')  
sma_data = calculate_sma(df, period=20)  
line.set(sma_data)  
rsi_chart = widget.create_subchart(height=0.3, width=1, sync=True)  
rsi_line = rsi_chart.create_line('RSI', color='#FF6B6B')  
rsi_line.set(calculate_rsi(df, period=14))

运行结果:

Pasted image 20250423185551.png

2.3 增加图例

widget.legend(True)
rsi_chart.legend(True)

Pasted image 20250423185813.png

2.4 多K线图支持

widget = QtChart(main_widget, inner_width=1, inner_height=0.5)  
widget.watermark(text='知识星球:创新技术阁')  
weekly_chart = widget.create_subchart(width=0.5, height=0.5,sync=True)  
weekly_chart.watermark(text='知识星球:创新技术阁')  
monthly_chart = widget.create_subchart(width=0.5, height=0.5,sync=True)  
monthly_chart.watermark(text='知识星球:创新技术阁')  
widget.legend(True)  
weekly_chart.legend(True)  
monthly_chart.legend(True)
widget.set(df)  
weekly_chart.set(df)  
monthly_chart.set(df)
  • • 创建两个子图:weekly_chart和monthly_chart;
  • • 主图使用inner_width控制宽度,1表示整个窗口宽度,inner_height控制高度,0.5表示一半窗口高度;
  • • 子图使用width控制宽度,0.5表示一半窗口宽度,height控制高度,0.5表示一半窗口高度

Pasted image 20250423190225.png

2.5 动态更新K线

def update_bar():  
    bar = new_data.pop(0)  
    new_bar = pd.Series([bar.datetime.strftime("%Y-%m-%d %H:%M:%S"), bar.open_price, bar.high_price, bar.low_price, bar.close_price, bar.volume],  
                        index=["time""open""high""low""close""volume"])  
    widget.update(new_bar)  
    weekly_chart.update(new_bar)  
    monthly_chart.update(new_bar)  
  
timer = QtCore.QTimer()  
timer.timeout.connect(update_bar)  
timer.start(1000)
  • • 使用定时器模拟数据更新;
  • • 使用update更新新的bar数据。

20250423_19-04-34.gif

2.6 回调函数

lightweight-charts-python 还支持图表事件的回调机制。你可以方便地实现用户交互功能,比如时间框架选择、搜索符号、横线位置调整等。例如,当用户选择不同的时间框架时,可以自动更新图表数据:

def on_timeframe_selection(chart): # 当用户改变timeframe的时候调用 
    if len(new_data) == 0:  
        return  
    chart.set(new_data, True)

3. 完整代码

#!/usr/bin/env python  
# encoding: utf-8  
  
"""  
@version: v1.0  
@author: Kandy.Ye  
@contact: Kandy.Ye@outlook.com  
@file: kline_test.py  
@time: 2025/4/9 9:02  
"""  
from datetime import datetime  
import pandas as pd  
import talib  
  
from gui.ui import QtCore  
from gui.ui.qt import QVBoxLayout, QMainWindow, QWidget, QApplication  
from core.trader.constant import Exchange, Interval  
from core.trader.database import get_database, BaseDatabase  
  
from lightweight_charts.widgets import QtChart  
  
def calculate_sma(df, period:int=20):  
    return pd.DataFrame({  
        'time': df['time'],  
        f'SMA{period}': df['close'].rolling(window=period).mean()  
    }).dropna()  
  
def calculate_rsi(df, period:int=14):  
    return pd.DataFrame({  
        'time': df['time'],  
        f'RSI': talib.RSI(df['close']timeperiod=14)  
    })  
  
if __name__ == "__main__":  
    app = QApplication([])  
    window = QMainWindow()  
    layout = QVBoxLayout()  
    main_widget = QWidget()  
    main_widget.setLayout(layout)  
    window.resize(800, 500)  
    layout.setContentsMargins(0, 0, 0, 0)  
  
    database: BaseDatabase = get_database()  
    bars = database.load_bar_data(  
        symbol="BTC-USDT-SWAP",  
        exchange=Exchange.OKX,  
        interval=Interval.MINUTE,  
        start=datetime(202437),  
        end=datetime(202491)  
    )
    
    n = 1000  
    history = bars[:n]  
    new_data = bars[n:]  
  
    data: list = []  
    for bar in history:  
        data.append([bar.datetime.strftime("%Y-%m-%d %H:%M:%S"), bar.open_price, bar.high_price, bar.low_price, bar.close_price, bar.volume])  
  
    df = pd.DataFrame(data, columns=["time""open""high""low""close""volume"])  
  
    widget = QtChart(main_widget, inner_width=1, inner_height=0.5)  
    widget.watermark(text='知识星球:创新技术阁')  
    weekly_chart = widget.create_subchart(width=0.5, height=0.5,sync=True)  
    weekly_chart.watermark(text='知识星球:创新技术阁')  
    monthly_chart = widget.create_subchart(width=0.5, height=0.5,sync=True)  
    monthly_chart.watermark(text='知识星球:创新技术阁')  
    widget.legend(True)  
    weekly_chart.legend(True)  
    monthly_chart.legend(True) 
    widget.set(df)  
    weekly_chart.set(df)  
    monthly_chart.set(df)  
    # line = widget.create_line('SMA20')  
    # sma_data = calculate_sma(df, period=20)
    # line.set(sma_data)    
    # rsi_chart = widget.create_subchart(height=0.3, width=1, sync=True)    
    # rsi_line = rsi_chart.create_line('RSI', color='#FF6B6B')    
    # rsi_line.set(calculate_rsi(df, period=14))    
    # rsi_chart.legend(True)  
    
    def update_bar():  
        bar = new_data.pop(0)  
        new_bar = pd.Series([bar.datetime.strftime("%Y-%m-%d %H:%M:%S"), bar.open_price, bar.high_price, bar.low_price, bar.close_price, bar.volume],  
                            index=["time""open""high""low""close""volume"])  
        widget.update(new_bar)  
        weekly_chart.update(new_bar)  
        monthly_chart.update(new_bar)  
  
    def on_timeframe_selection(chart): # Called when the user changes the timeframe.  
        if len(new_data) == 0:  
            return  
        chart.set(new_data, True)  
  
    timer = QtCore.QTimer()  
    timer.timeout.connect(update_bar)  
    timer.start(1000)  
  
    layout.addWidget(widget.get_webview())  
    window.setCentralWidget(main_widget)  
    window.show()  
    app.exec()

4. 联系方式