多因子打分¶
用多个因子综合打分,选分最高的买。多因子策略的骨架。
"""PE + 动量 + 波动率三因子打分选股"""
from qka import Data, Strategy, Broker, Backtest
class MultiFactor(Strategy):
def __init__(self, cash=1_000_000):
super().__init__(cash=cash)
self.last_month = None
def on_bar(self, date):
close = self.get('close')
if close is None or close.empty:
return
# 每月末调仓
if date.day < 28:
return
month_key = (date.year, date.month)
if self.last_month == month_key:
return
self.last_month = month_key
rsi = self.get('rsi_14')
hist = self.history('rsi_14', 20)
if hist is None or hist.empty:
return
scores = {}
for sym in close.index:
price = float(close[sym])
if price <= 0:
continue
if sym not in rsi.index or sym not in hist.columns:
continue
rsi_val = float(rsi[sym])
hist_series = hist[sym].dropna()
# 因子 1:RSI 适中(30-70 之间最好,太高中性,太低扣分)
score1 = 1.0 if 30 < rsi_val < 70 else 0.0
if rsi_val < 30:
score1 = -0.5
# 因子 2:RSI 趋势(最近 20 天 RSI 上升趋势)
if len(hist_series) >= 10:
recent = hist_series.iloc[-5:].mean()
earlier = hist_series.iloc[-10:-5].mean()
score2 = (recent - earlier) / earlier
score2 = max(-1, min(1, score2)) # 限制到 [-1, 1]
else:
score2 = 0
# 因子 3:波动率(低波动加分)
if len(hist_series) >= 10:
vol = hist_series.std()
score3 = max(-1, min(1, -vol / 20)) # 波动越低分越高
else:
score3 = 0
# 综合打分
scores[sym] = 0.4 * score1 + 0.4 * score2 + 0.2 * score3
if not scores:
return
# 选前 3 名
sorted_syms = sorted(scores, key=scores.get, reverse=True)
buy_list = sorted_syms[:3]
# 卖出不在名单里的
for sym in list(self.broker.positions.keys()):
if sym not in buy_list:
pos = self.broker.positions[sym]
price = float(close[sym])
if price > 0:
self.broker.sell(sym, price, pos['size'])
# 买入名单里的新标的
cash_per_sym = self.broker.cash / len(buy_list)
for sym in buy_list:
if sym in self.broker.positions:
continue
price = float(close[sym])
if price > 0:
size = self.sizing.fixed_amount(cash_per_sym, price)
if size > 0:
self.broker.buy(sym, price, size)
if __name__ == '__main__':
data = Data(
symbols=['000001.SZ', '600000.SH', '000002.SZ',
'600036.SH', '601166.SH', '600519.SH',
'000858.SZ', '002415.SZ', '300750.SZ'],
indicators={
'rsi_14': ('rsi', 14),
},
)
bt = Backtest(data, MultiFactor())
bt.run(benchmark='000300.SH')
bt.summary()
bt.report(title='多因子打分选股')