489 lines
15 KiB
Python
489 lines
15 KiB
Python
"""
|
||
技术指标因子 - 使用Polars实现
|
||
包含ATR、OBV、RSI、EMA等技术指标相关因子计算
|
||
"""
|
||
|
||
import polars as pl
|
||
import numpy as np
|
||
from typing import Dict, List, Optional, Any
|
||
from operator_framework import StockWiseOperator, OperatorConfig
|
||
import talib
|
||
|
||
|
||
class ATROperator(StockWiseOperator):
|
||
"""ATR算子"""
|
||
|
||
def __init__(self, period: int = 14):
|
||
config = OperatorConfig(
|
||
name=f"atr_{period}",
|
||
description=f"{period}日ATR",
|
||
required_columns=['high', 'low', 'close'],
|
||
output_columns=[f'atr_{period}'],
|
||
parameters={'period': period}
|
||
)
|
||
super().__init__(config)
|
||
self.period = period
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算ATR"""
|
||
# 使用TA-Lib计算ATR
|
||
atr_values = talib.ATR(
|
||
stock_df['high'].to_numpy(),
|
||
stock_df['low'].to_numpy(),
|
||
stock_df['close'].to_numpy(),
|
||
timeperiod=self.period
|
||
)
|
||
|
||
return stock_df.with_columns(pl.Series(atr_values).alias(f'atr_{self.period}'))
|
||
|
||
|
||
class OBVOperator(StockWiseOperator):
|
||
"""OBV算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="obv",
|
||
description="OBV能量潮",
|
||
required_columns=['close', 'vol'],
|
||
output_columns=['obv'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算OBV"""
|
||
# 使用TA-Lib计算OBV
|
||
obv_values = talib.OBV(
|
||
stock_df['close'].to_numpy(),
|
||
stock_df['vol'].to_numpy()
|
||
)
|
||
|
||
return stock_df.with_columns(pl.Series(obv_values).alias('obv'))
|
||
|
||
|
||
class OBVMAOperator(StockWiseOperator):
|
||
"""OBV均线算子"""
|
||
|
||
def __init__(self, period: int = 6):
|
||
config = OperatorConfig(
|
||
name=f"obv_ma_{period}",
|
||
description=f"{period}日OBV均线",
|
||
required_columns=['obv'],
|
||
output_columns=[f'maobv_{period}'],
|
||
parameters={'period': period}
|
||
)
|
||
super().__init__(config)
|
||
self.period = period
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算OBV均线"""
|
||
# 使用TA-Lib计算SMA
|
||
ma_values = talib.SMA(
|
||
stock_df['obv'].to_numpy(),
|
||
timeperiod=self.period
|
||
)
|
||
|
||
return stock_df.with_columns(pl.Series(ma_values).alias(f'maobv_{self.period}'))
|
||
|
||
|
||
class RSIOperator(StockWiseOperator):
|
||
"""RSI算子"""
|
||
|
||
def __init__(self, period: int = 3):
|
||
config = OperatorConfig(
|
||
name=f"rsi_{period}",
|
||
description=f"{period}日RSI",
|
||
required_columns=['close'],
|
||
output_columns=[f'rsi_{period}'],
|
||
parameters={'period': period}
|
||
)
|
||
super().__init__(config)
|
||
self.period = period
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算RSI"""
|
||
# 使用TA-Lib计算RSI
|
||
rsi_values = talib.RSI(
|
||
stock_df['close'].to_numpy(),
|
||
timeperiod=self.period
|
||
)
|
||
|
||
return stock_df.with_columns(pl.Series(rsi_values).alias(f'rsi_{self.period}'))
|
||
|
||
|
||
class EMAOperator(StockWiseOperator):
|
||
"""EMA算子"""
|
||
|
||
def __init__(self, period: int):
|
||
config = OperatorConfig(
|
||
name=f"ema_{period}",
|
||
description=f"{period}日EMA",
|
||
required_columns=['close'],
|
||
output_columns=[f'_ema_{period}'],
|
||
parameters={'period': period}
|
||
)
|
||
super().__init__(config)
|
||
self.period = period
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算EMA"""
|
||
# 使用TA-Lib计算EMA
|
||
ema_values = talib.EMA(
|
||
stock_df['close'].to_numpy(),
|
||
timeperiod=self.period
|
||
)
|
||
|
||
return stock_df.with_columns(pl.Series(ema_values).alias(f'_ema_{self.period}'))
|
||
|
||
|
||
class ReturnOperator(StockWiseOperator):
|
||
"""收益率算子"""
|
||
|
||
def __init__(self, period: int):
|
||
config = OperatorConfig(
|
||
name=f"return_{period}",
|
||
description=f"{period}日收益率",
|
||
required_columns=['close'],
|
||
output_columns=[f'return_{period}'],
|
||
parameters={'period': period}
|
||
)
|
||
super().__init__(config)
|
||
self.period = period
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算收益率"""
|
||
# 计算收益率
|
||
ret = pl.col('close').pct_change(self.period)
|
||
|
||
return stock_df.with_columns(ret.alias(f'return_{self.period}'))
|
||
|
||
|
||
class ActivityFactorOperator(StockWiseOperator):
|
||
"""活跃度因子算子"""
|
||
|
||
def __init__(self, period: int, scale: float):
|
||
config = OperatorConfig(
|
||
name=f"act_factor_{period}",
|
||
description=f"{period}日活跃度因子",
|
||
required_columns=[f'_ema_{period}'],
|
||
output_columns=[f'act_factor{period}'],
|
||
parameters={'period': period, 'scale': scale}
|
||
)
|
||
super().__init__(config)
|
||
self.period = period
|
||
self.scale = scale
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算活跃度因子"""
|
||
# 计算EMA变化率
|
||
ema_change = (pl.col(f'_ema_{self.period}') / pl.col(f'_ema_{self.period}').shift(1) - 1) * 100
|
||
|
||
# 计算活跃度因子
|
||
activity_factor = (ema_change * 57.3 / self.scale).arctan()
|
||
|
||
return stock_df.with_columns(activity_factor.alias(f'act_factor{self.period}'))
|
||
|
||
|
||
class ActivityFactor5Operator(StockWiseOperator):
|
||
"""活跃度因子5算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="act_factor_5",
|
||
description="5日活跃度因子",
|
||
required_columns=['_ema_5'],
|
||
output_columns=['act_factor1'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算5日活跃度因子"""
|
||
# 计算EMA变化率
|
||
ema_change = (pl.col('_ema_5') / pl.col('_ema_5').shift(1) - 1) * 100
|
||
|
||
# 计算活跃度因子
|
||
activity_factor = (ema_change * 57.3 / 50).arctan()
|
||
|
||
return stock_df.with_columns(activity_factor.alias('act_factor1'))
|
||
|
||
|
||
class ActivityFactor13Operator(StockWiseOperator):
|
||
"""活跃度因子13算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="act_factor_13",
|
||
description="13日活跃度因子",
|
||
required_columns=['_ema_13'],
|
||
output_columns=['act_factor2'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算13日活跃度因子"""
|
||
# 计算EMA变化率
|
||
ema_change = (pl.col('_ema_13') / pl.col('_ema_13').shift(1) - 1) * 100
|
||
|
||
# 计算活跃度因子
|
||
activity_factor = (ema_change * 57.3 / 40).arctan()
|
||
|
||
return stock_df.with_columns(activity_factor.alias('act_factor2'))
|
||
|
||
|
||
class ActivityFactor20Operator(StockWiseOperator):
|
||
"""活跃度因子20算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="act_factor_20",
|
||
description="20日活跃度因子",
|
||
required_columns=['_ema_20'],
|
||
output_columns=['act_factor3'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算20日活跃度因子"""
|
||
# 计算EMA变化率
|
||
ema_change = (pl.col('_ema_20') / pl.col('_ema_20').shift(1) - 1) * 100
|
||
|
||
# 计算活跃度因子
|
||
activity_factor = (ema_change * 57.3 / 21).arctan()
|
||
|
||
return stock_df.with_columns(activity_factor.alias('act_factor3'))
|
||
|
||
|
||
class ActivityFactor60Operator(StockWiseOperator):
|
||
"""活跃度因子60算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="act_factor_60",
|
||
description="60日活跃度因子",
|
||
required_columns=['_ema_60'],
|
||
output_columns=['act_factor4'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算60日活跃度因子"""
|
||
# 计算EMA变化率
|
||
ema_change = (pl.col('_ema_60') / pl.col('_ema_60').shift(1) - 1) * 100
|
||
|
||
# 计算活跃度因子
|
||
activity_factor = (ema_change * 57.3 / 10).arctan()
|
||
|
||
return stock_df.with_columns(activity_factor.alias('act_factor4'))
|
||
|
||
|
||
class ActivityFactor5and6Operator(StockWiseOperator):
|
||
"""活跃度因子5和6算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="act_factor_5_6",
|
||
description="活跃度因子5和6",
|
||
required_columns=['act_factor1', 'act_factor2'],
|
||
output_columns=['act_factor5', 'act_factor6'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算活跃度因子5和6"""
|
||
# 计算因子5
|
||
factor5 = pl.col('act_factor1') + pl.col('act_factor2') + pl.col('act_factor3') + pl.col('act_factor4')
|
||
|
||
# 计算因子6
|
||
numerator = pl.col('act_factor1') - pl.col('act_factor2')
|
||
denominator = (pl.col('act_factor1').pow(2) + pl.col('act_factor2').pow(2)).sqrt()
|
||
factor6 = numerator / (denominator + 1e-8) # 避免除零
|
||
|
||
return stock_df.with_columns([
|
||
factor5.alias('act_factor5'),
|
||
factor6.alias('act_factor6')
|
||
])
|
||
|
||
|
||
class Alpha003Operator(StockWiseOperator):
|
||
"""Alpha003算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="alpha_003",
|
||
description="Alpha003因子",
|
||
required_columns=['open', 'close', 'high', 'low'],
|
||
output_columns=['alpha_003'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算Alpha003"""
|
||
# 计算因子
|
||
alpha_003 = pl.when(pl.col('high') != pl.col('low')) \
|
||
.then((pl.col('close') - pl.col('open')) / (pl.col('high') - pl.col('low'))) \
|
||
.otherwise(0)
|
||
|
||
return stock_df.with_columns(alpha_003.alias('alpha_003'))
|
||
|
||
|
||
class Alpha007Operator(StockWiseOperator):
|
||
"""Alpha007算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="alpha_007",
|
||
description="Alpha007因子",
|
||
required_columns=['close', 'vol'],
|
||
output_columns=['alpha_007'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算Alpha007"""
|
||
# 计算5日相关性
|
||
corr_5 = pl.col('close').rolling_corr(pl.col('vol'), window=5)
|
||
|
||
return stock_df.with_columns(corr_5.alias('alpha_007'))
|
||
|
||
|
||
class Alpha013Operator(StockWiseOperator):
|
||
"""Alpha013算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="alpha_013",
|
||
description="Alpha013因子",
|
||
required_columns=['close'],
|
||
output_columns=['alpha_013'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算Alpha013"""
|
||
# 计算5日和20日和
|
||
sum_5 = pl.col('close').rolling_sum(window=5)
|
||
sum_20 = pl.col('close').rolling_sum(window=20)
|
||
|
||
# 计算因子
|
||
alpha_013 = sum_5 - sum_20
|
||
|
||
return stock_df.with_columns(alpha_013.alias('alpha_013'))
|
||
|
||
|
||
class Alpha022Operator(StockWiseOperator):
|
||
"""Alpha022算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="alpha_022",
|
||
description="Alpha022改进因子",
|
||
required_columns=['high', 'low', 'close', 'vol'],
|
||
output_columns=['alpha_22_improved'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算Alpha022改进因子"""
|
||
# 计算滚动协方差
|
||
cov_5 = pl.col('high').rolling_cov(pl.col('vol'), window=5)
|
||
|
||
# 计算协方差差分
|
||
delta_cov = cov_5.diff(5)
|
||
|
||
# 计算收盘价标准差
|
||
std_close = pl.col('close').rolling_std(window=20)
|
||
|
||
# 计算标准差排名 (简化版)
|
||
rank_std = std_close
|
||
|
||
# 计算最终因子
|
||
alpha_22 = -1 * delta_cov * rank_std
|
||
|
||
return stock_df.with_columns(alpha_22.alias('alpha_22_improved'))
|
||
|
||
|
||
class BBIRatioOperator(StockWiseOperator):
|
||
"""BBI比率算子"""
|
||
|
||
def __init__(self):
|
||
config = OperatorConfig(
|
||
name="bbi_ratio",
|
||
description="BBI比率因子",
|
||
required_columns=['close'],
|
||
output_columns=['bbi_ratio_factor'],
|
||
parameters={}
|
||
)
|
||
super().__init__(config)
|
||
|
||
def apply_stock(self, stock_df: pl.DataFrame, **kwargs) -> pl.DataFrame:
|
||
"""计算BBI比率"""
|
||
# 计算不同周期的SMA
|
||
sma3 = pl.col('close').rolling_mean(window=3)
|
||
sma6 = pl.col('close').rolling_mean(window=6)
|
||
sma12 = pl.col('close').rolling_mean(window=12)
|
||
sma24 = pl.col('close').rolling_mean(window=24)
|
||
|
||
# 计算BBI
|
||
bbi = (sma3 + sma6 + sma12 + sma24) / 4
|
||
|
||
# 计算比率
|
||
bbi_ratio = bbi / pl.col('close')
|
||
|
||
return stock_df.with_columns(bbi_ratio.alias('bbi_ratio_factor'))
|
||
|
||
|
||
# 技术指标因子集合
|
||
TECHNICAL_OPERATORS = [
|
||
ATROperator(14),
|
||
ATROperator(6),
|
||
OBVOperator(),
|
||
OBVMAOperator(6),
|
||
RSIOperator(3),
|
||
EMAOperator(5),
|
||
EMAOperator(13),
|
||
EMAOperator(20),
|
||
EMAOperator(60),
|
||
ReturnOperator(5),
|
||
ReturnOperator(20),
|
||
ActivityFactor5Operator(),
|
||
ActivityFactor13Operator(),
|
||
ActivityFactor20Operator(),
|
||
ActivityFactor60Operator(),
|
||
ActivityFactor5and6Operator(),
|
||
Alpha003Operator(),
|
||
Alpha007Operator(),
|
||
Alpha013Operator(),
|
||
Alpha022Operator(),
|
||
BBIRatioOperator(),
|
||
]
|
||
|
||
|
||
def apply_technical_factors(df: pl.DataFrame, operators: List = None) -> pl.DataFrame:
|
||
"""
|
||
应用所有技术指标因子
|
||
|
||
Args:
|
||
df: 输入的Polars DataFrame
|
||
operators: 要应用的算子列表,如果为None则使用默认列表
|
||
|
||
Returns:
|
||
添加了技术指标因子的DataFrame
|
||
"""
|
||
if operators is None:
|
||
operators = TECHNICAL_OPERATORS
|
||
|
||
result_df = df
|
||
for operator in operators:
|
||
result_df = operator(result_df)
|
||
|
||
return result_df
|