Files
etf/visualization/charts/indicators.py
aszerW 988c2335fb chore(config): 添加环境变量示例及.gitignore更新
- 新增 .env.example,包含 Tushare API、钉钉机器人和PostgreSQL数据库配置模板
- 更新.gitignore,忽略本地配置文件如 .env.local 和 config_local.py
- 添加对报表文件命名规则的支持,保留示例文件不忽略
- 删除废弃的 chart.py 及相关图表模块代码
- 新增 config/settings.py,实现从环境变量读取配置的统一接口
- 设置数据目录及缓存目录,确保目录存在,提高配置管理规范性
2026-03-18 23:33:40 +08:00

239 lines
6.3 KiB
Python

"""
技术指标绘制组件
"""
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)