zg / backtest /indicators.py
pigracing's picture
Upload 25 files
19a8231 verified
Raw History Blame Contribute Delete
6.85 kB
# -*- coding: utf-8 -*-
"""
技术指标计算模块
包含:MACD, KDJ, RSI, 布林带, 均线, 量比, 换手率, 振幅, 波动率等
"""
import numpy as np
import pandas as pd
import config
def calc_all_indicators(df: pd.DataFrame) -> pd.DataFrame:
"""一次性计算全部技术指标,返回带新列的 DataFrame"""
df = df.copy()
# 均线
for ma in config.MA_PERIODS:
df[f"ma{ma}"] = df["close"].rolling(ma).mean()
# 布林带
df = calc_bollinger(df)
# MACD
df = calc_macd(df)
# KDJ
df = calc_kdj(df)
# RSI
df["rsi14"] = calc_rsi(df["close"], 14)
# 成交量指标
df["volume_ma5"] = df["volume"].rolling(5).mean()
df["volume_ma20"] = df["volume"].rolling(20).mean()
df["volume_ma60"] = df["volume"].rolling(60).mean()
# 量比(当前量 / 20日均量)
df["vol_ratio"] = df["volume"] / df["volume"].shift(1).rolling(20).mean()
# 换手率(已有 turnover 列)
if "turnover" in df.columns:
df["turnover_ma5"] = df["turnover"].rolling(5).mean()
df["turnover_ma20"] = df["turnover"].rolling(20).mean()
# 振幅(使用前一日收盘价)
df["pre_close"] = df["close"].shift(1)
df["amplitude"] = (df["high"] - df["low"]) / df["pre_close"] * 100
# 涨跌幅
df["pct_chg"] = df["close"].pct_change() * 100
# 波动率(近20日涨跌幅绝对值均值)
df["volatility_20d"] = df["pct_chg"].abs().rolling(20).mean()
# 最高价/最低价 滚动
df["high_20"] = df["high"].rolling(20).max()
df["low_20"] = df["low"].rolling(20).min()
df["high_60"] = df["high"].rolling(60).max()
df["low_60"] = df["low"].rolling(60).min()
# 获利盘比例近似
df["profit_ratio"] = calc_profit_ratio(df)
# 筹码集中度近似(使用布林带宽度的逆指标和价格位置组合)
df["chip_concentration"] = calc_chip_concentration(df)
# V2.1.1: 两融指标(如有数据列)
if "margin_net_buy" in df.columns:
df["margin_ma5"] = df["margin_net_buy"].rolling(5).mean()
df["margin_ma20"] = df["margin_net_buy"].rolling(20).mean()
if "margin_short" in df.columns:
df["margin_short_ma5"] = df["margin_short"].rolling(5).mean()
return df
def calc_bollinger(df: pd.DataFrame, period: int = None, k: float = None) -> pd.DataFrame:
"""布林带指标"""
period = period or config.BOLLINGER_PERIOD
k = k or config.BOLLINGER_STD
df = df.copy()
df["boll_mid"] = df["close"].rolling(period).mean()
df["boll_std"] = df["close"].rolling(period).std()
df["boll_upper"] = df["boll_mid"] + k * df["boll_std"]
df["boll_lower"] = df["boll_mid"] - k * df["boll_std"]
df["boll_width"] = (df["boll_upper"] - df["boll_lower"]) / df["boll_mid"]
df["boll_pct"] = (df["close"] - df["boll_lower"]) / (df["boll_upper"] - df["boll_lower"])
df["boll_pct"] = df["boll_pct"].clip(0, 1)
return df
def calc_macd(df: pd.DataFrame) -> pd.DataFrame:
"""MACD 指标"""
df = df.copy()
fast = config.MACD_FAST
slow = config.MACD_SLOW
signal = config.MACD_SIGNAL
ema_fast = df["close"].ewm(span=fast, adjust=False).mean()
ema_slow = df["close"].ewm(span=slow, adjust=False).mean()
df["macd_dif"] = ema_fast - ema_slow
df["macd_dea"] = df["macd_dif"].ewm(span=signal, adjust=False).mean()
df["macd_bar"] = 2 * (df["macd_dif"] - df["macd_dea"])
return df
def calc_kdj(df: pd.DataFrame) -> pd.DataFrame:
"""KDJ 指标"""
df = df.copy()
period = config.KDJ_PERIOD
low_min = df["low"].rolling(period).min()
high_max = df["high"].rolling(period).max()
rsv = (df["close"] - low_min) / (high_max - low_min) * 100
rsv = rsv.fillna(50)
df["kdj_k"] = rsv.ewm(com=2, adjust=False).mean()
df["kdj_d"] = df["kdj_k"].ewm(com=2, adjust=False).mean()
df["kdj_j"] = 3 * df["kdj_k"] - 2 * df["kdj_d"]
return df
def calc_rsi(close_series: pd.Series, period: int = 14) -> pd.Series:
"""RSI 指标"""
delta = close_series.diff()
gain = delta.where(delta > 0, 0.0)
loss = (-delta).where(delta < 0, 0.0)
avg_gain = gain.ewm(span=period, adjust=False).mean()
avg_loss = loss.ewm(span=period, adjust=False).mean()
rs = avg_gain / avg_loss.replace(0, np.nan)
rsi = 100 - (100 / (1 + rs))
return rsi
def calc_profit_ratio(df: pd.DataFrame) -> pd.Series:
"""
获利盘比例近似计算
基于:当前价格在60日区间内的位置
profit_ratio = (close - low_60) / (high_60 - low_60)
"""
low_60 = df["low"].rolling(60).min()
high_60 = df["high"].rolling(60).max()
ratio = (df["close"] - low_60) / (high_60 - low_60).replace(0, np.nan)
return ratio.clip(0, 1)
def calc_chip_concentration(df: pd.DataFrame) -> pd.Series:
"""
筹码集中度近似计算
当价格在低位且振幅收窄、成交量萎缩时,视为筹码集中
指标含义:值越高表示筹码越集中(0~1)
"""
low_60 = df["low"].rolling(60).min()
high_60 = df["high"].rolling(60).max()
# 价格在低位(0~0.3区间为新低附近集中)
price_pos = (df["close"] - low_60) / (high_60 - low_60).replace(0, np.nan)
# 振幅收窄(近10日振幅均值与60日振幅均值的比值)
amp_10 = (df["high"] - df["low"]).rolling(10).mean()
amp_60 = (df["high"] - df["low"]).rolling(60).mean()
amp_ratio = amp_10 / amp_60.replace(0, np.nan)
amp_ratio = amp_ratio.clip(0, 1)
# 成交量萎缩(近5日均量是60日均量的比例,越低越集中)
vol_ratio = df["volume"].rolling(5).mean() / df["volume"].rolling(60).mean().replace(0, np.nan)
vol_ratio = 1 - vol_ratio.clip(0, 1) # 反转:量越小值越大
# 加权综合
concentration = (1 - price_pos.fillna(0.5)) * 0.4 + (1 - amp_ratio.fillna(0.5)) * 0.3 + vol_ratio.fillna(0.5) * 0.3
return concentration.clip(0, 1)
def get_support_resistance(df: pd.DataFrame) -> dict:
"""
识别支撑位和压力位
基于前期高点和低点
"""
if len(df) < 60:
return {"support": None, "resistance": None}
recent = df.tail(60)
# 支撑位:60日最低点
support = recent["low"].min()
# 压力位:60日最高点
resistance = recent["high"].max()
# 均线支撑:20日线位置
ma20 = recent["close"].rolling(20).mean().iloc[-1]
return {
"support": round(support, 2),
"resistance": round(resistance, 2),
"ma20": round(ma20, 2),
}