""" 技术指标绘制组件 """ import pandas as pd import numpy as np import talib as ta import random from lightweight_charts import Chart def get_fixed_color(num: int) -> str: """根据数字生成固定颜色""" random.seed(num) r = random.randint(0, 255) g = random.randint(0, 255) b = random.randint(0, 255) color = "#{:02x}{:02x}{:02x}".format(r, g, b) random.seed(None) return color def add_ema( chart: Chart, df: pd.DataFrame, period: int = 20, color: str = None, price_label: bool = False, ): """添加EMA指标线""" name = f"EMA_{period}" df[name] = ta.EMA(df["close"], timeperiod=period) line_color = color or get_fixed_color(period) line = chart.create_line( name, color=line_color, width=2, price_label=price_label, price_line=False ) line.set(df[["time", name]]) return line def add_cci( chart: Chart, df: pd.DataFrame, period: int = 14, height: float = 0.15, position: str = "bottom", ): """添加CCI副图""" 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.layout(font_family="Times New Roman") cci_chart.legend(visible=True, font_size=14, color="#FFFFFF") cci_chart.time_scale(visible=False) # CCI线 cci_line = cci_chart.create_line( name=f"CCI_{period}", color="#FF0000", width=2 ) cci_line.set(df[["time", f"CCI_{period}"]]) # 水平参考线 for level, label in [(100, "+100"), (-100, "-100")]: df[f"cci_{label}"] = level ref_line = cci_chart.create_line( name=label, color="#D4C21C", width=1, style="dashed", price_label=False, price_line=False ) ref_line.set(df[["time", f"cci_{label}"]]) return cci_chart def add_macd( chart: Chart, df: pd.DataFrame, fastperiod: int = 12, slowperiod: int = 26, signalperiod: int = 9, height: float = 0.15, position: str = "bottom", ): """添加MACD副图""" 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.layout(font_family="Times New Roman") macd_chart.legend(visible=True, font_size=14, color="#FFFFFF") macd_chart.time_scale(visible=False) # 柱状图 histogram = macd_chart.create_histogram(name=macd_name) hist_data = df[["time", macd_name]].copy() hist_data["prev_value"] = hist_data[macd_name].shift(1) def get_color(row): current, prev = row[macd_name], row["prev_value"] is_hollow = (current >= 0 and current < prev) or (current < 0 and current > prev) if current >= 0: return "rgba(255, 0, 0, 0.5)" if is_hollow else "#ff0000" else: return "rgba(0, 255, 0, 0.5)" if is_hollow else "#00FF00" hist_data["color"] = hist_data.apply(get_color, axis=1) hist_data = hist_data.drop("prev_value", axis=1) histogram.set(hist_data) # DIF线 dif_line = macd_chart.create_line( name="DIF", color="#2962FF", width=2, price_label=False, price_line=False ) dif_line.set(df[["time", "DIF"]]) # DEA线 dea_line = macd_chart.create_line( name="DEA", color="#FF0000", width=2, price_label=False, price_line=False ) dea_line.set(df[["time", "DEA"]]) return macd_chart def add_td_sequence(chart: Chart, df: pd.DataFrame): """添加TD序列标记""" close = df["close"].to_list() td = [0, 0, 0, 0] up = 0 down = 0 for i in range(4, len(close)): if close[i] > close[i - 4]: up += 1 down = 0 td.append(up) else: down -= 1 up = 0 td.append(down) df["TD"] = td # 添加标记 markers = [] for _, row in df.iterrows(): td_val = row["TD"] if td_val in [9, 13]: markers.append({ "time": row["time"].strftime("%Y-%m-%d %H:%M:%S"), "position": "above", "shape": "arrow_down", "color": "#00FF00", "text": str(td_val), }) elif td_val in [-9, -13]: markers.append({ "time": row["time"].strftime("%Y-%m-%d %H:%M:%S"), "position": "below", "shape": "arrow_up", "color": "#FF0000", "text": str(abs(td_val)), }) chart.marker_list(markers) def add_buy_sell_signals(chart: Chart, df: pd.DataFrame): """添加买卖信号标记""" if "buy" not in df.columns and "sell" not in df.columns: return markers = [] for _, row in df.iterrows(): if row.get("buy") == 1: markers.append({ "time": row["time"].strftime("%Y-%m-%d"), "position": "below", "shape": "arrow_up", "color": "#00FF00", "text": "B", }) elif row.get("sell") == 1: markers.append({ "time": row["time"].strftime("%Y-%m-%d"), "position": "above", "shape": "arrow_down", "color": "#FF0000", "text": "S", }) chart.marker_list(markers) class IndicatorOverlay: """指标叠加器""" def __init__(self, chart: Chart): self.chart = chart def add_default_indicators(self, df: pd.DataFrame): """添加默认指标组合""" # 短期EMA for period in [3, 5, 8, 10, 12, 15]: add_ema(self.chart, df, period=period, color=5) # 长期EMA for period in [30, 35, 40, 45, 50, 60]: add_ema(self.chart, df, period=period, color=10) # 年线 add_ema(self.chart, df, period=260) # MACD add_macd(self.chart, df, fastperiod=30, slowperiod=90) # CCI add_cci(self.chart, df, period=120) # TD序列 add_td_sequence(self.chart, df)