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>
This commit is contained in:
2026-06-07 15:59:05 +08:00
co-authored by Claude Opus 4.7
commit 271a9343a5
293 changed files with 59598 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
"""
振幅因子。
"""
import pandas as pd
from factors.base import BaseFactor
class AmplitudeFactor(BaseFactor):
"""N 日均振幅 = mean((high - low) / close, N) * 100"""
category = "technical"
def __init__(self, period: int = 5):
self.period = period
self.name = f"amplitude_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
daily_amp = (df["high"] - df["low"]) / df["close"].replace(0, float("nan")) * 100
return daily_amp.rolling(window=self.period, min_periods=self.period).mean()
def get_required_columns(self) -> list[str]:
return ["high", "low", "close"]
+47
View File
@@ -0,0 +1,47 @@
"""
ATR 平均真实波幅因子。
"""
import pandas as pd
from factors.base import BaseFactor
class ATRFactor(BaseFactor):
"""Average True Range,衡量波动性。"""
category = "technical"
def __init__(self, period: int = 14):
self.period = period
self.name = f"atr_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
high, low, close = df["high"], df["low"], df["close"]
prev_close = close.shift(1)
tr = pd.concat([
(high - low).abs(),
(high - prev_close).abs(),
(low - prev_close).abs(),
], axis=1).max(axis=1)
return tr.ewm(span=self.period, min_periods=self.period).mean()
def get_required_columns(self) -> list[str]:
return ["high", "low", "close"]
class ATRRatioFactor(BaseFactor):
"""ATR / close 归一化,便于跨股票比较。"""
category = "technical"
def __init__(self, period: int = 14):
self.period = period
self.name = f"atr_ratio_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
atr = ATRFactor(period=self.period).calculate(df)
return atr / df["close"].replace(0, float("nan")) * 100
def get_required_columns(self) -> list[str]:
return ["high", "low", "close"]
+53
View File
@@ -0,0 +1,53 @@
"""
布林带因子:价格在布林带中的位置。
"""
import pandas as pd
from factors.base import BaseFactor
class BollingerFactor(BaseFactor):
"""
布林带位置 = (close - middle) / (upper - lower)
值在 0~1 之间:接近 0 表示在下轨,接近 1 表示在上轨。
"""
category = "technical"
def __init__(self, period: int = 20, std: float = 2.0):
self.period = period
self.std = std
self.name = f"boll_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
middle = df["close"].rolling(window=self.period, min_periods=self.period).mean()
std = df["close"].rolling(window=self.period, min_periods=self.period).std()
upper = middle + self.std * std
lower = middle - self.std * std
band_width = upper - lower
return ((df["close"] - lower) / band_width.replace(0, float("nan"))).clip(0, 1)
def get_required_columns(self) -> list[str]:
return ["close"]
class BollingerWidthFactor(BaseFactor):
"""布林带宽度 = (upper - lower) / middle * 100"""
category = "technical"
def __init__(self, period: int = 20, std: float = 2.0):
self.period = period
self.std = std
self.name = f"boll_width_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
middle = df["close"].rolling(window=self.period, min_periods=self.period).mean()
std = df["close"].rolling(window=self.period, min_periods=self.period).std()
band_width = 2 * self.std * std
return band_width / middle.replace(0, float("nan")) * 100
def get_required_columns(self) -> list[str]:
return ["close"]
+49
View File
@@ -0,0 +1,49 @@
"""
均线交叉因子。
"""
import pandas as pd
from factors.base import BaseFactor
class MACrossFactor(BaseFactor):
"""
均线交叉信号。
返回:fast_ma / slow_ma - 1,正值表示短期均线在上方。
"""
category = "technical"
def __init__(self, fast: int = 5, slow: int = 20):
self.fast = fast
self.slow = slow
self.name = f"ma_cross_{fast}_{slow}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
ma_fast = df["close"].rolling(window=self.fast, min_periods=self.fast).mean()
ma_slow = df["close"].rolling(window=self.slow, min_periods=self.slow).mean()
return (ma_fast / ma_slow.replace(0, float("nan"))) - 1
def get_required_columns(self) -> list[str]:
return ["close"]
class MADeviationFactor(BaseFactor):
"""
价格偏离均线程度 = (close - ma) / ma * 100
"""
category = "technical"
def __init__(self, period: int = 20):
self.period = period
self.name = f"ma_dev_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
ma = df["close"].rolling(window=self.period, min_periods=self.period).mean()
return (df["close"] - ma) / ma.replace(0, float("nan")) * 100
def get_required_columns(self) -> list[str]:
return ["close"]
+47
View File
@@ -0,0 +1,47 @@
"""
MACD 因子。
"""
import pandas as pd
from factors.base import BaseFactor
class MACDFactor(BaseFactor):
"""
MACD 系列因子。
返回 DIF/DEA/HIST 三个值。使用 calculate() 返回 HIST(柱),
单独方法获取 DIF/DEA。
"""
category = "technical"
def __init__(self, fast: int = 12, slow: int = 26, signal: int = 9):
self.fast = fast
self.slow = slow
self.signal = signal
self.name = f"macd_{fast}_{slow}_{signal}"
def _ema(self, series: pd.Series, span: int) -> pd.Series:
return series.ewm(span=span, min_periods=span).mean()
def calculate(self, df: pd.DataFrame) -> pd.Series:
"""返回 MACD 柱(DIF - DEA)。"""
ema_fast = self._ema(df["close"], self.fast)
ema_slow = self._ema(df["close"], self.slow)
dif = ema_fast - ema_slow
dea = self._ema(dif, self.signal)
return dif - dea
def dif(self, df: pd.DataFrame) -> pd.Series:
ema_fast = self._ema(df["close"], self.fast)
ema_slow = self._ema(df["close"], self.slow)
return ema_fast - ema_slow
def dea(self, df: pd.DataFrame) -> pd.Series:
dif = self.dif(df)
return self._ema(dif, self.signal)
def get_required_columns(self) -> list[str]:
return ["close"]
+23
View File
@@ -0,0 +1,23 @@
"""
动量因子:N 日收益率。
"""
import pandas as pd
from factors.base import BaseFactor
class MomentumFactor(BaseFactor):
"""N 日价格动量 = (close_t - close_{t-N}) / close_{t-N} * 100"""
category = "technical"
def __init__(self, period: int = 20):
self.period = period
self.name = f"momentum_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
return df["close"].pct_change(periods=self.period) * 100
def get_required_columns(self) -> list[str]:
return ["close"]
+29
View File
@@ -0,0 +1,29 @@
"""
RSI 相对强弱因子。
"""
import pandas as pd
from factors.base import BaseFactor
class RSIFactor(BaseFactor):
"""Wilder's RSI = 100 - 100 / (1 + RS), RS = avg_gain / avg_loss"""
category = "technical"
def __init__(self, period: int = 14):
self.period = period
self.name = f"rsi_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
delta = df["close"].diff()
gain = delta.clip(lower=0)
loss = (-delta).clip(lower=0)
avg_gain = gain.ewm(span=self.period, min_periods=self.period).mean()
avg_loss = loss.ewm(span=self.period, min_periods=self.period).mean()
rs = avg_gain / avg_loss.replace(0, float("nan"))
return 100 - 100 / (1 + rs)
def get_required_columns(self) -> list[str]:
return ["close"]
+40
View File
@@ -0,0 +1,40 @@
"""
换手率因子。
"""
import pandas as pd
from factors.base import BaseFactor
class TurnoverFactor(BaseFactor):
"""N 日均换手率。"""
category = "technical"
def __init__(self, period: int = 5):
self.period = period
self.name = f"turnover_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
return df["turnover_rate"].rolling(window=self.period, min_periods=self.period).mean()
def get_required_columns(self) -> list[str]:
return ["turnover_rate"]
class TurnoverChangeFactor(BaseFactor):
"""换手率变化 = 当日换手率 / N 日均换手率。"""
category = "technical"
def __init__(self, period: int = 5):
self.period = period
self.name = f"turnover_chg_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
avg = df["turnover_rate"].rolling(window=self.period, min_periods=self.period).mean()
return df["turnover_rate"] / avg.replace(0, float("nan"))
def get_required_columns(self) -> list[str]:
return ["turnover_rate"]
+42
View File
@@ -0,0 +1,42 @@
"""
波动率因子。
"""
import pandas as pd
from factors.base import BaseFactor
class VolatilityFactor(BaseFactor):
"""N 日年化波动率 = std(daily_return, N) * sqrt(252) * 100"""
category = "technical"
def __init__(self, period: int = 20):
self.period = period
self.name = f"volatility_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
daily_ret = df["close"].pct_change()
return daily_ret.rolling(window=self.period, min_periods=self.period).std() * (252 ** 0.5) * 100
def get_required_columns(self) -> list[str]:
return ["close"]
class DownsideVolatilityFactor(BaseFactor):
"""下行波动率:只计算负收益的标准差。"""
category = "technical"
def __init__(self, period: int = 20):
self.period = period
self.name = f"down_vol_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
daily_ret = df["close"].pct_change()
downside = daily_ret.clip(upper=0)
return downside.rolling(window=self.period, min_periods=self.period).std() * (252 ** 0.5) * 100
def get_required_columns(self) -> list[str]:
return ["close"]
+40
View File
@@ -0,0 +1,40 @@
"""
量价因子:量比 / 成交量变化率。
"""
import pandas as pd
from factors.base import BaseFactor
class VolumeFactor(BaseFactor):
"""N 日均量比 = vol / mean(vol, N)"""
category = "technical"
def __init__(self, period: int = 5):
self.period = period
self.name = f"vol_ratio_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
avg_vol = df["vol"].rolling(window=self.period, min_periods=self.period).mean()
return df["vol"] / avg_vol.replace(0, float("nan"))
def get_required_columns(self) -> list[str]:
return ["vol"]
class VolumeChangeFactor(BaseFactor):
"""成交量 N 日变化率"""
category = "technical"
def __init__(self, period: int = 5):
self.period = period
self.name = f"vol_chg_{period}"
def calculate(self, df: pd.DataFrame) -> pd.Series:
return df["vol"].pct_change(periods=self.period) * 100
def get_required_columns(self) -> list[str]:
return ["vol"]