diff --git a/chart.py b/chart.py index df068f4..80a1bd8 100644 --- a/chart.py +++ b/chart.py @@ -41,7 +41,7 @@ def add_ema(df, chart, period: int = 50): 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["cci"] = cci + df["CCI"] = cci cci_chart = chart.create_subchart( position=position, width=1, height=height, sync=True ) @@ -49,8 +49,8 @@ def add_cci(df, chart, period: int = 14, height: float = 0.1, position: str = "b 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="cci", color="#FF0000", width=2) - cci_line.set(df[["time", "cci"]]) + cci_line = cci_chart.create_line(name="CCI", color="#FF0000", width=2) + cci_line.set(df[["time", "CCI"]]) df = df[["time"]].copy() df["h"] = 100 df["l"] = -100 @@ -74,13 +74,24 @@ def add_cci(df, chart, period: int = 14, height: float = 0.1, position: str = "b cci_line.set(df[["time", "l"]]) -def add_macd(df, chart, height: float = 0.1, position: str = "bottom"): +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=12, slowperiod=26, signalperiod=9 + df["close"], + fastperiod=fastperiod, + slowperiod=slowperiod, + signalperiod=signalperiod, ) - df["macd"] = macd - df["signal"] = signal - df["histogram"] = hist + df["DIF"] = macd + df["DEA"] = signal + df["MACD"] = hist * 2 macd_chart = chart.create_subchart( position=position, width=1, height=height, sync=True ) @@ -89,21 +100,21 @@ def add_macd(df, chart, height: float = 0.1, position: str = "bottom"): ) macd_chart.time_scale(visible=False) - histogram = macd_chart.create_histogram(name="histogram") - hist_data = df[["time", "histogram"]].copy() - hist_data["color"] = hist_data["histogram"].apply( + histogram = macd_chart.create_histogram(name="MACD") + hist_data = df[["time", "MACD"]].copy() + hist_data["color"] = hist_data["MACD"].apply( lambda x: "#00FF00" if x < 0 else "#ff0000" # 绿色 : 红色 ) histogram.set(hist_data) macd_line = macd_chart.create_line( - name="macd", color="#2962FF", width=2, price_label=False, price_line=False + name="DIF", color="#2962FF", width=2, price_label=False, price_line=False ) - macd_line.set(df[["time", "macd"]]) + macd_line.set(df[["time", "DIF"]]) signal_line = macd_chart.create_line( - name="signal", color="#FF0000", width=2, price_label=False, price_line=False + name="DEA", color="#FF0000", width=2, price_label=False, price_line=False ) - signal_line.set(df[["time", "signal"]]) + signal_line.set(df[["time", "DEA"]]) def TD(dataframe: pd.DataFrame): @@ -260,18 +271,49 @@ def resample_data(df: pd.DataFrame, timeframe: str) -> pd.DataFrame: def POC(df, bins: int = 50): + """ + 计算价格分布的成交量峰值位置(POC,Point Of Control) + + 参数: + df: 包含 'low', 'high', 'volume' 三列的 DataFrame(每行代表一根 K 线) + bins: 将价格区间划分为多少个小区间(价格桶),默认为 50 + + 返回: + poc: 代表成交量最大的价格区间的中点价格 + """ + + # 当前数据的最低价和最高价,用于构建价格区间 low, high = df["low"].min(), df["high"].max() - edges = np.linspace(low, high, bins + 1) - # 为每根K线生成 bins 个均匀价格点,并分配等量成交量 - prices = np.concatenate( - [np.linspace(r["low"], r["high"], bins) for _, r in df.iterrows()] - ) - volumes = np.repeat(df["volume"].values / bins, bins) - # 分箱求和 - vol_profile, _ = np.histogram(prices, bins=edges, weights=volumes) - # 返回最大成交量区间的中点 - idx = np.argmax(vol_profile) - poc = (edges[idx] + edges[idx + 1]) / 2 + # 将整个价格区间等分为 bins 个小区间,需要 bins+1 个边界点 + price_ranges = np.linspace(low, high, bins + 1) + + # 初始化每个价格区间累积的成交量数组,长度为 bins(区间个数) + volume_per_price = np.zeros(bins) + + # 遍历每一根 K 线,将该 K 线的成交量按覆盖的价格区间平均分配 + for i in range(len(df)): + high = df["high"].iloc[i] + low = df["low"].iloc[i] + volume = df["volume"].iloc[i] + + # 找到当前 K 线覆盖的价格边界(注意使用闭区间判断) + # price_ranges 表示边界点,若 price_ranges[j] 在 [low, high] 范围内,则第 j 个边界被覆盖 + price_coverage = (price_ranges >= low) & (price_ranges <= high) + covered_bins = np.where(price_coverage)[0] + + if len(covered_bins) > 0: + # 如果覆盖了多个边界点,则这些边界之间形成了若干完整的区间 + # 将该 K 线的成交量平均分配到这些被覆盖的区间上 + # 注意:covered_bins 里是边界索引,最后一个边界索引对应的区间索引需小于 bins + volume_per_bin = volume / len(covered_bins) + for bin_idx in covered_bins: + if bin_idx < bins: + volume_per_price[bin_idx] += volume_per_bin + + # 找到成交量最大的区间索引 + idx = np.argmax(volume_per_price) + # 以该区间的两个边界的中点作为 POC 值 + poc = (price_ranges[idx] + price_ranges[idx + 1]) / 2 return poc @@ -281,6 +323,7 @@ def on_range_change_poc(chart, bars_before, bars_after): return total_bars = len(df) + # TODO: k线拉到最早会报错 if bars_after < 0: start_idx = max(0, int(bars_before)) end_idx = total_bars @@ -333,7 +376,7 @@ def plot_chart(df, symbol: str, name: str, timeframe: str): add_ema(df, chart, period=30) add_ema(df, chart, period=60) add_macd(df, chart) - add_cci(df, chart, period=14) + add_cci(df, chart, period=26) add_TD(df, chart) add_buy_sell_signal_markers(df, chart) @@ -341,7 +384,7 @@ def plot_chart(df, symbol: str, name: str, timeframe: str): if __name__ == "__main__": - symbol = "399986" + symbol = "399998" timeframe = "1W" df = pd.read_csv(