修改为类;添加update_legend函数;添加实时鼠标y轴价格获取并计算该价格和可见范围内的最新收盘价之间的利润Crosshair Price Profit%

This commit is contained in:
2025-10-19 11:59:05 +08:00
parent bcfffe4181
commit e363332827

451
chart.py
View File

@@ -9,115 +9,6 @@ from lightweight_charts import Chart
import random import random
from datetime import datetime from datetime import datetime
horizontal_lines = {}
def get_fixed_color_based_on_period(period):
# 使用周期值作为随机种子,确保相同周期生成相同颜色
random.seed(period)
# 生成随机的RGB值
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
# 将RGB值转换为十六进制颜色代码
color_code = "#{:02x}{:02x}{:02x}".format(r, g, b)
# 重置随机种子,避免影响其他随机操作
random.seed(None)
return color_code
def add_ema(df, chart, period: int = 50):
name = f"EMA_{period}"
df[name] = ta.EMA(df["close"], timeperiod=period)
color = get_fixed_color_based_on_period(period)
line = chart.create_line(
name, color=color, width=2, price_label=False, price_line=False
)
line.set(df[["time", name]])
def add_cci(df, chart, period: int = 14, height: float = 0.1, position: str = "bottom"):
cci = ta.CCI(df["high"], df["low"], df["close"], timeperiod=period)
df[f"CCI_{period}"] = cci
cci_chart = chart.create_subchart(
position=position, width=1, height=height, sync=True
)
cci_chart.legend(
visible=True, font_size=14, color="#FFFFFF", font_family="Times New Roman"
)
cci_chart.time_scale(visible=False)
cci_line = cci_chart.create_line(name=f"CCI_{period}", color="#FF0000", width=2)
cci_line.set(df[["time", f"CCI_{period}"]])
df = df[["time"]].copy()
df["h"] = 100
df["l"] = -100
cci_line = cci_chart.create_line(
name="h",
color="#D4C21C",
width=1,
style="dashed",
price_label=False,
price_line=False,
)
cci_line.set(df[["time", "h"]])
cci_line = cci_chart.create_line(
name="l",
color="#D4C21C",
width=1,
style="dashed",
price_label=False,
price_line=False,
)
cci_line.set(df[["time", "l"]])
def add_macd(
df,
chart,
fastperiod: int = 12,
slowperiod: int = 26,
signalperiod: int = 9,
height: float = 0.1,
position: str = "bottom",
):
macd, signal, hist = ta.MACD(
df["close"],
fastperiod=fastperiod,
slowperiod=slowperiod,
signalperiod=signalperiod,
)
df["DIF"] = macd
df["DEA"] = signal
macd_name = f"MACD_{fastperiod}_{slowperiod}_{signalperiod}"
df[macd_name] = hist * 2
macd_chart = chart.create_subchart(
position=position, width=1, height=height, sync=True
)
macd_chart.legend(
visible=True, font_size=14, color="#FFFFFF", font_family="Times New Roman"
)
macd_chart.time_scale(visible=False)
histogram = macd_chart.create_histogram(name=macd_name)
hist_data = df[["time", macd_name]].copy()
hist_data["color"] = hist_data[macd_name].apply(
lambda x: "#00FF00" if x < 0 else "#ff0000" # 绿色 : 红色
)
histogram.set(hist_data)
macd_line = macd_chart.create_line(
name="DIF", color="#2962FF", width=2, price_label=False, price_line=False
)
macd_line.set(df[["time", "DIF"]])
signal_line = macd_chart.create_line(
name="DEA", color="#FF0000", width=2, price_label=False, price_line=False
)
signal_line.set(df[["time", "DEA"]])
def TD(dataframe: pd.DataFrame): def TD(dataframe: pd.DataFrame):
close = dataframe["close"].to_list() close = dataframe["close"].to_list()
@@ -136,77 +27,6 @@ def TD(dataframe: pd.DataFrame):
return td return td
def add_TD(df, chart):
df["TD"] = TD(df)
td_line = chart.create_line(
name="TD",
color="rgba(0, 0, 0, 0)", # 透明色,不在图表上显示线条
width=0,
price_line=False,
price_label=False,
price_scale_id="td_scale",
)
td_line.precision(0)
td_line.set(df[["time", "TD"]])
TDs = df[["time", "TD"]].to_dict(orient="records")
markers = []
for item in TDs:
if item["TD"] in [9, 13]:
markers.append(
{
"time": item["time"].strftime("%Y-%m-%d"),
"position": "above",
"shape": "arrow_down",
"color": "#00FF00",
"text": f"{item['TD']}",
}
)
elif item["TD"] in [-9, -13]:
markers.append(
{
"time": item["time"].strftime("%Y-%m-%d"),
"position": "below",
"shape": "arrow_up",
"color": "#FF0000",
"text": f"{item['TD']}",
}
)
chart.marker_list(markers)
def add_buy_sell_signal_markers(df, chart):
if "buy" not in df.columns or "sell" not in df.columns:
return
markers = []
signals = df[["time", "buy", "sell"]].to_dict(orient="records")
for item in signals:
if item["buy"] == 1:
markers.append(
{
"time": item["time"].strftime("%Y-%m-%d"),
"position": "below",
"shape": "arrow_up",
"color": "#00FF00",
"text": f"B",
}
)
elif item["sell"] == 1:
markers.append(
{
"time": item["time"].strftime("%Y-%m-%d"),
"position": "above",
"shape": "arrow_down",
"color": "#FF0000",
"text": f"S",
}
)
chart.marker_list(markers)
def resample_data(df: pd.DataFrame, timeframe: str) -> pd.DataFrame: def resample_data(df: pd.DataFrame, timeframe: str) -> pd.DataFrame:
""" """
对日线数据进行重采样 对日线数据进行重采样
@@ -296,7 +116,196 @@ def POC(df, bins: int = 50):
return poc return poc
def on_range_change_poc(chart, bars_before, bars_after): def get_fixed_color_based_on_period(num: int):
# 使用周期值作为随机种子,确保相同周期生成相同颜色
random.seed(num)
# 生成随机的RGB值
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
# 将RGB值转换为十六进制颜色代码
color_code = "#{:02x}{:02x}{:02x}".format(r, g, b)
# 重置随机种子,避免影响其他随机操作
random.seed(None)
return color_code
class QuantChart:
def __init__(self):
self.horizontal_lines = {}
self.legend_data = {}
self.visible_latest_close = None
def update_legend(self, chart, key, text):
self.legend_data[key] = text
sorted_dict = dict(sorted(self.legend_data.items()))
full_text = ", ".join(sorted_dict.values())
chart.legend(visible=True, text=full_text)
def add_ema(self, df, chart, period: int = 50):
name = f"EMA_{period}"
df[name] = ta.EMA(df["close"], timeperiod=period)
color = get_fixed_color_based_on_period(num=period)
line = chart.create_line(
name, color=color, width=2, price_label=False, price_line=False
)
line.set(df[["time", name]])
def add_cci(
self, df, chart, period: int = 14, height: float = 0.1, position: str = "bottom"
):
cci = ta.CCI(df["high"], df["low"], df["close"], timeperiod=period)
df[f"CCI_{period}"] = cci
cci_chart = chart.create_subchart(
position=position, width=1, height=height, sync=True
)
cci_chart.legend(
visible=True, font_size=14, color="#FFFFFF", font_family="Times New Roman"
)
cci_chart.time_scale(visible=False)
cci_line = cci_chart.create_line(name=f"CCI_{period}", color="#FF0000", width=2)
cci_line.set(df[["time", f"CCI_{period}"]])
df = df[["time"]].copy()
df["h"] = 100
df["l"] = -100
cci_line = cci_chart.create_line(
name="h",
color="#D4C21C",
width=1,
style="dashed",
price_label=False,
price_line=False,
)
cci_line.set(df[["time", "h"]])
cci_line = cci_chart.create_line(
name="l",
color="#D4C21C",
width=1,
style="dashed",
price_label=False,
price_line=False,
)
cci_line.set(df[["time", "l"]])
def add_macd(
self,
df,
chart,
fastperiod: int = 12,
slowperiod: int = 26,
signalperiod: int = 9,
height: float = 0.1,
position: str = "bottom",
):
macd, signal, hist = ta.MACD(
df["close"],
fastperiod=fastperiod,
slowperiod=slowperiod,
signalperiod=signalperiod,
)
df["DIF"] = macd
df["DEA"] = signal
macd_name = f"MACD_{fastperiod}_{slowperiod}_{signalperiod}"
df[macd_name] = hist * 2
macd_chart = chart.create_subchart(
position=position, width=1, height=height, sync=True
)
macd_chart.legend(
visible=True, font_size=14, color="#FFFFFF", font_family="Times New Roman"
)
macd_chart.time_scale(visible=False)
histogram = macd_chart.create_histogram(name=macd_name)
hist_data = df[["time", macd_name]].copy()
hist_data["color"] = hist_data[macd_name].apply(
lambda x: "#00FF00" if x < 0 else "#ff0000" # 绿色 : 红色
)
histogram.set(hist_data)
macd_line = macd_chart.create_line(
name="DIF", color="#2962FF", width=2, price_label=False, price_line=False
)
macd_line.set(df[["time", "DIF"]])
signal_line = macd_chart.create_line(
name="DEA", color="#FF0000", width=2, price_label=False, price_line=False
)
signal_line.set(df[["time", "DEA"]])
def add_TD(self, df, chart):
df["TD"] = TD(df)
td_line = chart.create_line(
name="TD",
color="rgba(0, 0, 0, 0)", # 透明色,不在图表上显示线条
width=0,
price_line=False,
price_label=False,
price_scale_id="td_scale",
)
td_line.precision(0)
td_line.set(df[["time", "TD"]])
TDs = df[["time", "TD"]].to_dict(orient="records")
markers = []
for item in TDs:
if item["TD"] in [9, 13]:
markers.append(
{
"time": item["time"].strftime("%Y-%m-%d"),
"position": "above",
"shape": "arrow_down",
"color": "#00FF00",
"text": f"{item['TD']}",
}
)
elif item["TD"] in [-9, -13]:
markers.append(
{
"time": item["time"].strftime("%Y-%m-%d"),
"position": "below",
"shape": "arrow_up",
"color": "#FF0000",
"text": f"{item['TD']}",
}
)
chart.marker_list(markers)
def add_buy_sell_signal_markers(self, df, chart):
if "buy" not in df.columns or "sell" not in df.columns:
return
markers = []
signals = df[["time", "buy", "sell"]].to_dict(orient="records")
for item in signals:
if item["buy"] == 1:
markers.append(
{
"time": item["time"].strftime("%Y-%m-%d"),
"position": "below",
"shape": "arrow_up",
"color": "#00FF00",
"text": f"B",
}
)
elif item["sell"] == 1:
markers.append(
{
"time": item["time"].strftime("%Y-%m-%d"),
"position": "above",
"shape": "arrow_down",
"color": "#FF0000",
"text": f"S",
}
)
chart.marker_list(markers)
def on_range_change_poc(self, chart, bars_before, bars_after):
df = chart.candle_data df = chart.candle_data
if df is None or df.empty: if df is None or df.empty:
return return
@@ -316,23 +325,24 @@ def on_range_change_poc(chart, bars_before, bars_after):
# logger.info(f"df_range_len: {len(df_range)}") # logger.info(f"df_range_len: {len(df_range)}")
poc = POC(df_range) poc = POC(df_range)
poc_line_name = "POC" poc_line_name = "POC"
if poc_line_name in horizontal_lines: if poc_line_name in self.horizontal_lines:
horizontal_lines[poc_line_name].delete() self.horizontal_lines[poc_line_name].delete()
# 添加新的 POC 水平线 # 添加新的 POC 水平线
latest_close = df_range["close"].iloc[-1] self.visible_latest_close = df_range["close"].iloc[-1]
profit = (latest_close - poc) / poc * 100 profit = (self.visible_latest_close - poc) / poc * 100
poc_line = chart.horizontal_line( poc_line = chart.horizontal_line(
price=poc, color="#FF0000", width=4, style="solid", text=f"{poc:.2f}" price=poc, color="#FF0000", width=4, style="solid", text=f"{poc:.2f}"
) )
chart.legend( # chart.legend(
visible=True, # visible=True,
text=f"POC: {poc:.2f}, poc_range_profit%: {profit:.1f}%, tf_cnt: {len(df_range)}", # text=f"POC: {poc:.2f}, poc_range_profit%: {profit:.1f}%, visible_tf_cnt: {len(df_range)}",
) # )
legend_text = f"POC: {poc:.2f}, poc_range_profit%: {profit:.1f}%, visible_tf_cnt: {len(df_range)}"
self.update_legend(chart=chart, key=poc_line_name, text=legend_text)
horizontal_lines[poc_line_name] = poc_line self.horizontal_lines[poc_line_name] = poc_line
def check_df(self, df: pd.DataFrame):
def check_df(df: pd.DataFrame):
# basic type check # basic type check
if not isinstance(df, pd.DataFrame): if not isinstance(df, pd.DataFrame):
raise TypeError("df must be a pandas DataFrame") raise TypeError("df must be a pandas DataFrame")
@@ -348,7 +358,9 @@ def check_df(df: pd.DataFrame):
time_series = df["time"] time_series = df["time"]
if not ( if not (
pd.api.types.is_datetime64_any_dtype(time_series) pd.api.types.is_datetime64_any_dtype(time_series)
or time_series.apply(lambda x: isinstance(x, (pd.Timestamp, datetime))).all() or time_series.apply(
lambda x: isinstance(x, (pd.Timestamp, datetime))
).all()
): ):
raise TypeError( raise TypeError(
"Column 'time' must contain datetime values (python datetime.datetime or pandas.Timestamp) " "Column 'time' must contain datetime values (python datetime.datetime or pandas.Timestamp) "
@@ -360,12 +372,43 @@ def check_df(df: pd.DataFrame):
if not pd.api.types.is_numeric_dtype(df[col]): if not pd.api.types.is_numeric_dtype(df[col]):
raise TypeError(f"Column '{col}' must be numeric (int or float)") raise TypeError(f"Column '{col}' must be numeric (int or float)")
def setup_crosshair_tracking(self, chart):
chart.run_script(
f"""
{chart.id}.chart.subscribeCrosshairMove((param) => {{
if (!param.point) return;
const price = {chart.id}.series.coordinateToPrice(param.point.y);
if (price !== null) {{
window.callbackFunction(`crosshair_price_~_${{price}}`);
}}
}})
"""
)
def plot_chart( # 注册回调处理
df, symbol: str, name: str, timeframe: str, init_visible_num_bars: int = 90 def on_crosshair_price(price_str):
): # 实时获取鼠标位置y轴的价格
price = float(price_str)
if self.visible_latest_close is not None:
profit = (self.visible_latest_close - price) / price * 100
self.update_legend(
chart=chart,
key="Crosshair Price Profit%",
text=f"Crosshair Price Profit%: {profit:.2f}%",
)
chart.win.handlers["crosshair_price"] = lambda p: on_crosshair_price(p)
def plot_chart(
self,
df,
symbol: str,
name: str,
timeframe: str,
init_visible_num_bars: int = 90,
):
# 校验数据是否满足 # 校验数据是否满足
check_df(df) self.check_df(df)
chart = Chart(toolbox=True, inner_height=0.8, maximize=True) chart = Chart(toolbox=True, inner_height=0.8, maximize=True)
chart.topbar.textbox("symbol", symbol) chart.topbar.textbox("symbol", symbol)
@@ -382,17 +425,19 @@ def plot_chart(
chart.set_visible_range(start_time, end_time) chart.set_visible_range(start_time, end_time)
# 设置每次放缩k线范围时的回调函数计算实时计算poc # 设置每次放缩k线范围时的回调函数计算实时计算poc
chart.events.range_change += on_range_change_poc chart.events.range_change += self.on_range_change_poc
self.setup_crosshair_tracking(chart)
# 添加技术指标 # 添加技术指标
# add_ema(df, chart, period=10) # add_ema(df, chart, period=10)
# add_ema(df, chart, period=20) # add_ema(df, chart, period=20)
add_ema(df, chart, period=30) self.add_ema(df, chart, period=30)
# add_ema(df, chart, period=60) # add_ema(df, chart, period=60)
add_macd(df, chart) self.add_macd(df, chart)
add_cci(df, chart, period=14) self.add_cci(df, chart, period=14)
add_TD(df, chart) self.add_TD(df, chart)
add_buy_sell_signal_markers(df, chart) self.add_buy_sell_signal_markers(df, chart)
chart.show(block=True) chart.show(block=True)