Files
etf/core/factors/technical.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

208 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
技术指标计算模块
包含CCI、EMA、MACD等常用技术指标
"""
import pandas as pd
import numpy as np
import talib as ta
def calculate_cci(
df: pd.DataFrame,
period: int = 14,
high_col: str = "high",
low_col: str = "low",
close_col: str = "close",
) -> pd.Series:
"""
计算CCI指标商品通道指数
Args:
df: DataFrame with OHLC data
period: CCI周期
high_col: 最高价列名
low_col: 最低价列名
close_col: 收盘价列名
Returns:
Series: CCI值
"""
return ta.CCI(
high=df[high_col],
low=df[low_col],
close=df[close_col],
timeperiod=period,
)
def calculate_ema(
price_series: pd.Series,
period: int = 20,
) -> pd.Series:
"""
计算指数移动平均线
Args:
price_series: 价格序列
period: EMA周期
Returns:
Series: EMA值
"""
return ta.EMA(price_series, timeperiod=period)
def calculate_macd(
price_series: pd.Series,
fastperiod: int = 12,
slowperiod: int = 26,
signalperiod: int = 9,
) -> tuple[pd.Series, pd.Series, pd.Series]:
"""
计算MACD指标
Args:
price_series: 价格序列
fastperiod: 快线周期
slowperiod: 慢线周期
signalperiod: 信号线周期
Returns:
tuple: (macd, signal, hist)
"""
macd, signal, hist = ta.MACD(
price_series,
fastperiod=fastperiod,
slowperiod=slowperiod,
signalperiod=signalperiod,
)
return macd, signal, hist
def calculate_td_sequence(close_series: pd.Series) -> pd.Series:
"""
计算TD序列Tom DeMark Sequential
Args:
close_series: 收盘价序列
Returns:
Series: TD序列值正数为上涨计数负数为下跌计数
"""
close = close_series.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)
return pd.Series(td, index=close_series.index)
def resample_to_weekly(df: pd.DataFrame) -> pd.DataFrame:
"""
将日线数据重采样为周线数据
Args:
df: DataFrame with columns: date, open, high, low, close, volume
Returns:
DataFrame: 周线数据
"""
df = df.copy()
if "date" in df.columns:
df["date"] = pd.to_datetime(df["date"])
df.set_index("date", inplace=True)
weekly = pd.DataFrame(
{
"code": df["code"].resample("W").first() if "code" in df.columns else None,
"open": df["open"].resample("W").first(),
"high": df["high"].resample("W").max(),
"low": df["low"].resample("W").min(),
"close": df["close"].resample("W").last(),
"volume": df["volume"].resample("W").sum(),
}
)
return weekly.dropna()
class TechnicalScreener:
"""技术指标筛选器基类"""
def __init__(self, name: str):
self.name = name
def screen(self, df: pd.DataFrame) -> bool:
"""
判断数据是否符合筛选条件
Args:
df: DataFrame with OHLCV data
Returns:
bool: 是否符合条件
"""
raise NotImplementedError
class CCIScreener(TechnicalScreener):
"""CCI超卖筛选器"""
def __init__(
self,
day_period: int = 14,
week_period: int = 14,
threshold: float = -100,
use_weekly: bool = True,
):
super().__init__("CCI超卖筛选")
self.day_period = day_period
self.week_period = week_period
self.threshold = threshold
self.use_weekly = use_weekly
def screen(self, df: pd.DataFrame) -> dict:
"""
筛选CCI超卖信号
Returns:
dict: {
'triggered': bool, # 是否触发信号
'day_cci': float, # 日线CCI值
'week_cci': float, # 周线CCI值如启用
}
"""
# 计算日线CCI
day_cci = calculate_cci(df, period=self.day_period).iloc[-1]
result = {
"triggered": day_cci < self.threshold,
"day_cci": day_cci,
"week_cci": None,
}
# 计算周线CCI如果启用
if self.use_weekly:
weekly_df = resample_to_weekly(df)
if len(weekly_df) >= self.week_period:
week_cci = calculate_cci(weekly_df, period=self.week_period).iloc[-1]
result["week_cci"] = week_cci
# 日线或周线任一超卖即触发
result["triggered"] = (
day_cci < self.threshold or week_cci < self.threshold
)
return result