Files
MoFin/deploy/profile-scripts/indicators.py
T

92 lines
3.2 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.
#!/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),EMA 平滑——与 backtest_framework.calc_atr 完全一致
2026-08-12 统一口径:原 SMA 与回测 EMA 差 3.59%,回测实盘口径分裂)。
klines: [{high, low, close, ...}]。返回最新 ATR 值(单值)。"""
if len(klines) < n + 1:
return None
highs = [k["high"] for k in klines]
lows = [k["low"] for k in klines]
closes = [k["close"] for k in klines]
# True Range(与 backtest_framework.calc_tr 一致)
tr = [highs[0] - lows[0]]
for i in range(1, len(highs)):
hl = highs[i] - lows[i]
hc = abs(highs[i] - closes[i - 1])
lc = abs(lows[i] - closes[i - 1])
tr.append(max(hl, hc, lc))
# EMA 平滑(与 backtest_framework.calc_atr 一致:calc_ema(tr, n)
k = 2 / (n + 1)
ema = tr[0]
for t in tr[1:]:
ema = t * k + ema * (1 - k)
return ema
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