Files
myquant/finance/backtest/signal.py
T
simonandClaude Opus 4.7 271a9343a5 Initial commit: cc-cursor 全链路量化研究平台
7 Sprints 全部完成:
  Sprint 0: 基础设施 (DataManager + MariaDB)
  Sprint 1: 因子引擎 (34因子/12分类)
  Sprint 2: VectorBT 回测 (5策略+截面)
  Sprint 3: Optuna 优化 (+Walk-Forward)
  Sprint 4: ML 模型 (LightGBM+CatBoost)
  Sprint 5: Qwen 情绪因子 (三源新闻+日期对齐)
  Sprint 6: Agent 系统 (4Agent+日报.md/.html)

生产加固 (15项): Tushare双源fallback, SSH自动恢复, pool_pre_ping,
  save_daily先删后插, load_dotenv绝对路径, 日报5d/20d修复,
  RiskAgent改上证指数, 昨日对比+数据截止, mac_report utf8mb4,
  CLAUDE-*.md 9条已知Bug, demo全参数化, djapi数据源归一化,
  indexDatas API修正

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 15:59:05 +08:00

116 lines
3.0 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.
"""
信号生成工具函数。
因子值 → 交易信号的桥梁,纯函数无副作用。
"""
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'=向下穿越
返回:
信号 Series1=买入, 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)