refactor: 工具函数抽取公共模块——indicators.py(指标)+market_data.py(数据),mr/s2扫描器改为公共模块import(消除策略扫描器互import)

This commit is contained in:
hmo
2026-08-11 08:20:09 +08:00
parent af7419bc77
commit 0ef84d34d5
4 changed files with 171 additions and 138 deletions
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""indicators.py — 通用技术指标库(2026-08-11 从 mr_scanner 抽取)
背景:calc_ma/calc_rsi/calc_atr/calc_obv 原定义在 mr_scanner.py
被 s2_scanner.py import 复用 —— 策略扫描器互相 import 工具函数是坏味道。
抽取到公共模块,供所有策略扫描器(mr/s2/accumulation/p_oversold)共用。
算法与 backtest_framework.py 完全一致(零偏差)。
"""
def calc_ma(series, n):
"""简单移动平均。前 n-1 位返回 None。"""
result = []
for i in range(len(series)):
if i < n - 1:
result.append(None)
else:
result.append(sum(series[i - n + 1:i + 1]) / n)
return result
def calc_rsi(series, n=14):
"""RSI(相对强弱指数),Wilder 平滑。"""
deltas = [series[i] - series[i - 1] for i in range(1, len(series))]
gains = [d if d > 0 else 0 for d in deltas]
losses = [-d if d < 0 else 0 for d in deltas]
result = [None] * (n + 1)
avg_gain = sum(gains[:n]) / n
avg_loss = sum(losses[:n]) / n
if avg_loss == 0:
result.append(100)
else:
rs = avg_gain / avg_loss
result.append(100 - 100 / (1 + rs))
for i in range(n, len(gains)):
avg_gain = (avg_gain * (n - 1) + gains[i]) / n
avg_loss = (avg_loss * (n - 1) + losses[i]) / n
if avg_loss == 0:
result.append(100)
else:
rs = avg_gain / avg_loss
result.append(100 - 100 / (1 + rs))
while len(result) < len(series):
result.insert(0, None)
return result[:len(series)]
def calc_atr(klines, n=14):
"""ATRAverage True Range),与回测 calc_factors 的 atr_pct 同口径。
klines: [{high, low, close, ...}]"""
if len(klines) < n + 1:
return None
trs = []
for i in range(1, len(klines)):
h, l, pc = klines[i]["high"], klines[i]["low"], klines[i - 1]["close"]
tr = max(h - l, abs(h - pc), abs(l - pc))
trs.append(tr)
atr = sum(trs[-n:]) / n
return atr
def calc_obv(klines):
"""OBV 能量潮(近20日变化量),资金流向指标。
klines: [{close, volume, ...}]"""
if len(klines) < 21:
return 0
obv = 0
for i in range(1, len(klines)):
if klines[i]["close"] > klines[i - 1]["close"]:
obv += klines[i]["volume"] * 100 # 手→股
elif klines[i]["close"] < klines[i - 1]["close"]:
obv -= klines[i]["volume"] * 100
# 近20日 OBV 变化
obv_now = 0
for i in range(max(1, len(klines) - 20), len(klines)):
if klines[i]["close"] > klines[i - 1]["close"]:
obv_now += klines[i]["volume"] * 100
elif klines[i]["close"] < klines[i - 1]["close"]:
obv_now -= klines[i]["volume"] * 100
return obv_now