""" 信号生成工具函数。 因子值 → 交易信号的桥梁,纯函数无副作用。 """ import pandas as pd def factor_to_threshold_signal( factor_series: pd.Series, buy_threshold: float, sell_threshold: float | None = None, cross_direction: str = "up", ) -> pd.Series: """ 因子阈值交叉信号。 参数: factor_series: 因子值 Series buy_threshold: 买入阈值(如 RSI < 30 则买) sell_threshold: 卖出阈值(如 RSI > 70 则卖),None 表示平所有仓 cross_direction: 'up'=因子向上穿越阈值时触发, 'down'=向下穿越 返回: 信号 Series:1=买入, 0=平仓 """ signals = pd.Series(0, index=factor_series.index) if cross_direction == "down": buys = factor_series < buy_threshold else: buys = factor_series > buy_threshold signals[buys] = 1 if sell_threshold is not None: if cross_direction == "down": sells = factor_series > sell_threshold else: sells = factor_series < sell_threshold signals[sells] = 0 # 过滤连续信号 signals = _filter_consecutive(signals) return signals def factor_to_quantile_signal( factor_series: pd.Series, top_quantile: float = 0.8, bottom_quantile: float = 0.2, ) -> pd.Series: """ 因子分位数信号 — 按滚动分位数判断。 参数: factor_series: 因子值 top_quantile: 高于此分位买入 bottom_quantile: 低于此分位平仓 返回: 信号 Series """ top = factor_series.quantile(top_quantile) bottom = factor_series.quantile(bottom_quantile) signals = pd.Series(0, index=factor_series.index) signals[factor_series > top] = 1 signals[factor_series < bottom] = 0 return _filter_consecutive(signals) def cross_signal( fast: pd.Series, slow: pd.Series, ) -> pd.Series: """ 金叉/死叉信号。 fast 上穿 slow → 买入(1) fast 下穿 slow → 平仓(0) """ fast = fast.dropna() slow = slow.dropna() common_idx = fast.index.intersection(slow.index) fast, slow = fast[common_idx], slow[common_idx] signals = pd.Series(-1, index=common_idx) above = (fast > slow).fillna(False) above = above.infer_objects(copy=False) # 交叉点:今天 above=True 且昨天 above=False → 金叉 prev = above.shift(1).fillna(False) prev = prev.infer_objects(copy=False) cross_up = above & ~prev cross_down = ~above & prev signals[cross_up] = 1 signals[cross_down] = 0 return _filter_consecutive(signals) def _filter_consecutive(signals: pd.Series) -> pd.Series: """过滤连续相同信号,只保留首次出现的信号。""" result = signals.copy() prev = None for i in range(len(result)): if result.iloc[i] == prev: result.iloc[i] = -1 # 标记为不操作 else: prev = result.iloc[i] return result[result != -1].reindex(signals.index).fillna(-1)