Files
easy_tdx_max/src/easy_tdx/MyTT.py
T
Justin Gu e374a0da28 release: v1.32.6 — 两周改动深度审查全面修复(回测口径三件套/LLM 安全加固/涨停价舍入/时区统一/缓存与竞态等 58 处)
对 v1.21→v1.32.5 的 249 文件 4.2 万行改动做六路专项审查,本轮落地全部发现:

回测正确性:组合收益 fillna(0) 虚增、轮动停牌日过期价成交、单标的 WF 逐窗指标
被预热区稀释(三件套均带先红后绿回归);worst_drawdown 方向、grading 容错、
组合体检品种费率、寻优端点费率透传。

安全:LLM api_url 仅 http/https 且禁 userinfo(封死 file:// 读取与 Key 外送链)、
错误响应不回显原始 body、响应体 2MB 上限、配置原子写、坏配置字段级防御。

数据:涨跌停价整数分币舍入(67/318/90 个价位错 1 分漏判清零)、交易时段/采样/
provisional 统一沪时区、warehouse 增量缺口自动全量重拉、provisional 定点转正、
baostock 真故障抛错 + W/M 去 tradestatus(实测服务端报错,周月兜底此前从未工作)
+ 指数 vol 股→手(实测锚定)、ccpm 结构变更抛错。

Web API:缓存键补 count/vipdoc、NaN 清洗先于缓存、count>800 分页取全量、
submit 透传真实状态、pending 不再被淘汰成幽灵、watchlist/server 入参约束。

公式:FILTER 去副作用、0-1 值域误判收严、递归深度上限、REF 负移位显式禁止。

前端:4 处请求竞态序号守卫、Sparkline viewBox、北交所 market=2 映射、
空数据缓存死角、AI 弹窗卸载中止轮询、量能/资金日历口径修正。

CLI/CI:warehouse sync 失败 exit 1、参数校验干净报错、release 真实发布 SHA256、
CI 超时与缓存、spec 补 baostock 前提。

约 60 条回归测试先红后绿;pytest 1820 全过,ruff/mypy/vue-tsc/node --test 全绿。
2026-09-06 22:16:48 +08:00

785 lines
29 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.
# MyTT 麦语言-通达信-同花顺指标实现 https://github.com/mpquant/MyTT
# MyTT高级函数验证版本: https://github.com/mpquant/MyTT/blob/main/MyTT_plus.py
# Python2老版本pandas特别的MyTT https://github.com/mpquant/MyTT/blob/main/MyTT_python2.py
# V2.1 2021-6-6 新增 BARSLAST函数 SLOPE,FORCAST线性回归预测函数
# V2.3 2021-6-13 新增 TRIX,DPO,BRAR,DMA,MTM,MASS,ROC,VR,ASI等指标
# V2.4 2021-6-27 新增 EXPMA,OBV,MFI指标, 改进SMA核心函数(核心函数彻底无循环)
# V2.7 2021-11-21 修正 SLOPE,BARSLAST,函数,新加FILTER,LONGCROSS,
# 感谢qzhjiang对SLOPE,SMA等函数的指正
# V2.8 2021-11-23 修正 FORCAST,WMA函数,欢迎qzhjiang,stanene,bcq加入社群,一起来完善myTT库
# V2.9 2021-11-29 新增 HHVBARS,LLVBARS,CONST, VALUEWHEN功能函数
# V2.92 2021-11-30 新增 BARSSINCEN函数,现在可以 pip install MyTT 完成安装
# V3.0 2021-12-04 改进 DMA函数支持序列,新增XS2 薛斯通道II指标
# V3.1 2021-12-19 新增 TOPRANGE,LOWRANGE一级函数
# V3.2 2023-04-04 新增 CR指标
# V3.3 2023-11-09 新增 SIN,COS,TAN序列处理的三角函数
# V4.0 2026-06-02 handsomejustin 新增 ZHUOYAO,BIAS_SIGNAL两个自创函数
# V4.1 2026-06-14 新增 SAR(抛物线转向), VWAP(成交量加权均价), AROON(阿隆指标); 注册 FK
# V4.2 2026-07-09 新增 FSL(分水岭指标)
# V4.3 2026-09-03 新增无未来函数指标16个:HMA,KAMA,SUPERTREND,CHANDELIER,ICHIMOKU,
# UOS,CMO,TSI,FISHER,SQUEEZE,CHOP,AD,CMF,EFI,BBP,BBW(全部只引用当期及历史数据)
# 以下所有函数如无特别说明,输入参数S均为numpy序列或者列表list,N为整型int
# 应用层1级函数完美兼容通达信或同花顺,具体使用方法请参考通达信
import numpy as np
import pandas as pd
# ------------------ 0级:核心工具函数 --------------------------------------------
def RD(N, D=3):
return np.round(N, D) # 四舍五入取3位小数
def RET(S, N=1):
return np.array(S)[-N] # 返回序列倒数第N个值,默认返回最后一个
def ABS(S):
return np.abs(S) # 返回N的绝对值
def LN(S):
return np.log(S) # 求底是e的自然对数,
def POW(S, N):
return np.power(S, N) # 求S的N次方
def SQRT(S):
return np.sqrt(S) # 求S的平方根
def SIN(S):
return np.sin(S) # 求S的正弦值(弧度)
def COS(S):
return np.cos(S) # 求S的余弦值(弧度)
def TAN(S):
return np.tan(S) # 求S的正切值(弧度)
def MAX(S1, S2):
return np.maximum(S1, S2) # 序列max
def MIN(S1, S2):
return np.minimum(S1, S2) # 序列min
def IF(S, A, B):
return np.where(S, A, B) # 序列布尔判断 return=A if S==True else B
def REF(S, N=1): # 对序列整体下移动N,返回序列(shift后会产生NAN)
return pd.Series(S).shift(N).values
def DIFF(S, N=1): # 前一个值减后一个值,前面会产生nan
return pd.Series(S).diff(N).values # np.diff(S)直接删除nan,会少一行
def STD(S, N): # 求序列的N日标准差,返回序列
return pd.Series(S).rolling(N).std(ddof=0).values
def SUM(S, N): # 对序列求N天累计和,返回序列 N=0对序列所有依次求和
return pd.Series(S).rolling(N).sum().values if N > 0 else pd.Series(S).cumsum().values
def CONST(S): # 返回序列S最后的值组成常量序列
return np.full(len(S), S[-1])
def HHV(S, N): # HHV(C, 5) 最近5天收盘最高价
return pd.Series(S).rolling(N).max().values
def LLV(S, N): # LLV(C, 5) 最近5天收盘最低价
return pd.Series(S).rolling(N).min().values
def HHVBARS(S, N): # 求N周期内S最高值到当前周期数, 返回序列
return pd.Series(S).rolling(N).apply(lambda x: np.argmax(x[::-1]), raw=True).values
def LLVBARS(S, N): # 求N周期内S最低值到当前周期数, 返回序列
return pd.Series(S).rolling(N).apply(lambda x: np.argmin(x[::-1]), raw=True).values
def MA(S, N): # 求序列的N日简单移动平均值,返回序列
return pd.Series(S).rolling(N).mean().values
def EMA(S, N): # 指数移动平均,为了精度 S>4*N EMA至少需要120周期 alpha=2/(span+1)
return pd.Series(S).ewm(span=N, adjust=False).mean().values
def SMA(S, N, M=1): # 中国式的SMA,至少需要120周期才精确 (雪球180周期) alpha=1/(1+com)
return pd.Series(S).ewm(alpha=M / N, adjust=False).mean().values # com=N-M/M
def WMA(S, N): # 通达信S序列的N日加权移动平均 Yn = (1*X1+2*X2+3*X3+...+n*Xn)/(1+2+3+...+Xn)
return (
pd.Series(S)
.rolling(N)
.apply(lambda x: x[::-1].cumsum().sum() * 2 / N / (N + 1), raw=True)
.values
)
def DMA(S, A): # 求S的动态移动平均,A作平滑因子,必须 0<A<1 (此为核心函数,非指标)
if isinstance(A, int | float):
return pd.Series(S).ewm(alpha=A, adjust=False).mean().values
A = np.array(A)
A[np.isnan(A)] = 1.0
Y = np.zeros(len(S))
Y[0] = S[0]
for i in range(1, len(S)):
Y[i] = A[i] * S[i] + (1 - A[i]) * Y[i - 1] # A支持序列 by jqz1226
return Y
def AVEDEV(S, N): # 平均绝对偏差 (序列与其平均值的绝对差的平均值)
return pd.Series(S).rolling(N).apply(lambda x: (np.abs(x - x.mean())).mean()).values
def SLOPE(S, N): # 返S序列N周期回线性回归斜率
return (
pd.Series(S).rolling(N).apply(lambda x: np.polyfit(range(N), x, deg=1)[0], raw=True).values
)
def FORCAST(S, N): # 返回S序列N周期回线性回归后的预测值, jqz1226改进成序列出
return (
pd.Series(S)
.rolling(N)
.apply(lambda x: np.polyval(np.polyfit(range(N), x, deg=1), N - 1), raw=True)
.values
)
def LAST(S, A, B): # 从前A日到前B日一直满足S_BOOL条件, 要求A>B & A>0 & B>=0
return np.array(
pd.Series(S).rolling(A + 1).apply(lambda x: np.all(x[::-1][B:]), raw=True), dtype=bool
)
# -- 1级:应用层函数(通过0级核心函数实现)使用方法请参考通达信 --------------------
def COUNT(S, N): # COUNT(CLOSE>O, N): 最近N天满足S_BOO的天数 True的天数
return SUM(S, N)
def EVERY(S, N): # EVERY(CLOSE>O, 5) 最近N天是否都是True
return IF(SUM(S, N) == N, True, False)
def EXIST(S, N): # EXIST(CLOSE>3010, N=5) n日内是否存在一天大于3000点
return IF(SUM(S, N) > 0, True, False)
def FILTER(S, N): # FILTER函数,S满足条件后,将其后N周期内的数据置为0, FILTER(C==H,5)
# 无副作用实现:在副本上置零。曾直接改写输入序列——公式通道里
# FILTER(C, N) 会把同一公式后续语句引用的 C 一并污染(或对只读
# 数组直接报错)。
out = np.array(S, copy=True)
for i in range(len(out)):
out[i + 1 : i + 1 + N] = 0 if out[i] else out[i + 1 : i + 1 + N]
return out # 例:FILTER(C==H,5) 涨停后,后5天不再发出信号
def BARSLAST(S): # 上一次条件成立到当前的周期, BARSLAST(C/REF(C,1)>=1.1) 上一次涨停到今天的天数
M = np.concatenate(([0], np.where(S, 1, 0)))
for i in range(1, len(M)):
M[i] = 0 if M[i] else M[i - 1] + 1
return M[1:]
def BARSLASTCOUNT(S): # 统计连续满足S条件的周期数 by jqz1226
rt = np.zeros(len(S) + 1) # BARSLASTCOUNT(CLOSE>OPEN)表示统计连续收阳的周期数
for i in range(len(S)):
rt[i + 1] = rt[i] + 1 if S[i] else rt[i + 1]
return rt[1:]
def BARSSINCEN(S, N): # N周期内第一次S条件成立到现在的周期数,N为常量 by jqz1226
return (
pd.Series(S)
.rolling(N)
.apply(lambda x: N - 1 - np.argmax(x) if np.argmax(x) or x[0] else 0, raw=True)
.fillna(0)
.values.astype(int)
)
def CROSS(
S1, S2
): # 判断向上金叉穿越 CROSS(MA(C,5),MA(C,10)) 判断向下死叉穿越 CROSS(MA(C,10),MA(C,5))
return np.concatenate(
([False], np.logical_not((S1 > S2)[:-1]) & (S1 > S2)[1:])
) # 不使用0级函数,移植方便 by jqz1226
def LONGCROSS(
S1, S2, N
): # 两条线维持一定周期后交叉,S1在N周期内都小于S2,本周期从S1下方向上穿过S2时返回1,否则返回0
return np.array(
np.logical_and(LAST(S1 < S2, N, 1), (S1 > S2)), dtype=bool
) # N=1时等同于CROSS(S1, S2)
def VALUEWHEN(S, X): # 当S条件成立时,取X的当前值,否则取VALUEWHEN的上个成立时的X值 by jqz1226
return pd.Series(np.where(S, X, np.nan)).ffill().values
def BETWEEN(S, A, B): # S处于A和B之间时为真。 包括 A<S<B 或 A>S>B
return ((A < S) & (S < B)) | ((A > S) & (S > B))
def TOPRANGE(S): # TOPRANGE(HIGH)表示当前最高价是近多少周期内最高价的最大值 by jqz1226
rt = np.zeros(len(S))
for i in range(1, len(S)):
rt[i] = np.argmin(np.flipud(S[:i] < S[i]))
return rt.astype("int")
def LOWRANGE(S): # LOWRANGE(LOW)表示当前最低价是近多少周期内最低价的最小值 by jqz1226
rt = np.zeros(len(S))
for i in range(1, len(S)):
rt[i] = np.argmin(np.flipud(S[:i] > S[i]))
return rt.astype("int")
# ------------------ 2级:技术指标函数(全部通过0级,1级函数实现) ------------------------------
def MACD(CLOSE, SHORT=12, LONG=26, M=9): # EMA的关系,S取120日,和雪球小数点2位相同
DIF = EMA(CLOSE, SHORT) - EMA(CLOSE, LONG)
DEA = EMA(DIF, M)
MACD = (DIF - DEA) * 2
return RD(DIF), RD(DEA), RD(MACD)
def KDJ(CLOSE, HIGH, LOW, N=9, M1=3, M2=3): # KDJ指标
low_n = LLV(LOW, N)
high_n = HHV(HIGH, N)
high_low_diff = high_n - low_n
# 避免除零:当最高价等于最低价时,RSV 应该为 50(中性)
with np.errstate(divide="ignore", invalid="ignore"):
rsv = (CLOSE - low_n) / high_low_diff * 100
rsv = np.where(high_low_diff == 0, 50, rsv) # 除零时返回 50
K = EMA(rsv, (M1 * 2 - 1))
D = EMA(K, (M2 * 2 - 1))
J = K * 3 - D * 2
return K, D, J
def RSI(CLOSE, N=24): # RSI指标,和通达信小数点2位相同
DIF = CLOSE - REF(CLOSE, 1)
abs_dif_sma = SMA(ABS(DIF), N)
# 避免除零:当价格完全不变时,RSI 应该为 50(中性)
with np.errstate(divide="ignore", invalid="ignore"):
rsi_value = SMA(MAX(DIF, 0), N) / abs_dif_sma * 100
rsi_value = np.where(abs_dif_sma == 0, 50, rsi_value) # 除零时返回 50
return RD(rsi_value)
def WR(CLOSE, HIGH, LOW, N=10, N1=6): # W&R 威廉指标
high_n = HHV(HIGH, N)
low_n = LLV(LOW, N)
high_low_diff = high_n - low_n
with np.errstate(divide="ignore", invalid="ignore"):
wr = (high_n - CLOSE) / high_low_diff * 100
wr = np.where(high_low_diff == 0, 50, wr) # 除零时返回 50
high_n1 = HHV(HIGH, N1)
low_n1 = LLV(LOW, N1)
high_low_diff1 = high_n1 - low_n1
with np.errstate(divide="ignore", invalid="ignore"):
wr1 = (high_n1 - CLOSE) / high_low_diff1 * 100
wr1 = np.where(high_low_diff1 == 0, 50, wr1) # 除零时返回 50
return RD(wr), RD(wr1)
def BIAS(CLOSE, L1=6, L2=12, L3=24): # BIAS乖离率
BIAS1 = (CLOSE - MA(CLOSE, L1)) / MA(CLOSE, L1) * 100
BIAS2 = (CLOSE - MA(CLOSE, L2)) / MA(CLOSE, L2) * 100
BIAS3 = (CLOSE - MA(CLOSE, L3)) / MA(CLOSE, L3) * 100
return RD(BIAS1), RD(BIAS2), RD(BIAS3)
def BOLL(CLOSE, N=20, P=2): # BOLL指标,布林带
MID = MA(CLOSE, N)
UPPER = MID + STD(CLOSE, N) * P
LOWER = MID - STD(CLOSE, N) * P
return RD(UPPER), RD(MID), RD(LOWER)
def PSY(CLOSE, N=12, M=6):
PSY = COUNT(CLOSE > REF(CLOSE, 1), N) / N * 100
PSYMA = MA(PSY, M)
return RD(PSY), RD(PSYMA)
def CCI(CLOSE, HIGH, LOW, N=14):
TP = (HIGH + LOW + CLOSE) / 3
return (TP - MA(TP, N)) / (0.015 * AVEDEV(TP, N))
def ATR(CLOSE, HIGH, LOW, N=20): # 真实波动N日平均值
TR = MAX(MAX((HIGH - LOW), ABS(REF(CLOSE, 1) - HIGH)), ABS(REF(CLOSE, 1) - LOW))
return MA(TR, N)
def BBI(CLOSE, M1=3, M2=6, M3=12, M4=20): # BBI多空指标
return (MA(CLOSE, M1) + MA(CLOSE, M2) + MA(CLOSE, M3) + MA(CLOSE, M4)) / 4
def DMI(CLOSE, HIGH, LOW, M1=14, M2=6): # 动向指标:结果和同花顺,通达信完全一致
TR = SUM(MAX(MAX(HIGH - LOW, ABS(HIGH - REF(CLOSE, 1))), ABS(LOW - REF(CLOSE, 1))), M1)
HD = HIGH - REF(HIGH, 1)
LD = REF(LOW, 1) - LOW
DMP = SUM(IF((HD > 0) & (HD > LD), HD, 0), M1)
DMM = SUM(IF((LD > 0) & (LD > HD), LD, 0), M1)
PDI = DMP * 100 / TR
MDI = DMM * 100 / TR
ADX = MA(ABS(MDI - PDI) / (PDI + MDI) * 100, M2)
ADXR = (ADX + REF(ADX, M2)) / 2
return PDI, MDI, ADX, ADXR
def TAQ(HIGH, LOW, N): # 唐安奇通道(海龟)交易指标,大道至简,能穿越牛熊
UP = HHV(HIGH, N)
DOWN = LLV(LOW, N)
MID = (UP + DOWN) / 2
return UP, MID, DOWN
def KTN(CLOSE, HIGH, LOW, N=20, M=10): # 肯特纳交易通道, N选20日,ATR选10日
MID = EMA((HIGH + LOW + CLOSE) / 3, N)
ATRN = ATR(CLOSE, HIGH, LOW, M)
UPPER = MID + 2 * ATRN
LOWER = MID - 2 * ATRN
return UPPER, MID, LOWER
def TRIX(CLOSE, M1=12, M2=20): # 三重指数平滑平均线
TR = EMA(EMA(EMA(CLOSE, M1), M1), M1)
TRIX = (TR - REF(TR, 1)) / REF(TR, 1) * 100
TRMA = MA(TRIX, M2)
return TRIX, TRMA
def VR(CLOSE, VOL, M1=26): # VR容量比率
LC = REF(CLOSE, 1)
return SUM(IF(CLOSE > LC, VOL, 0), M1) / SUM(IF(CLOSE <= LC, VOL, 0), M1) * 100
def CR(CLOSE, HIGH, LOW, N=20): # CR价格动量指标
MID = REF(HIGH + LOW + CLOSE, 1) / 3
num = SUM(MAX(0, HIGH - MID), N)
den = SUM(MAX(0, MID - LOW), N)
with np.errstate(divide="ignore", invalid="ignore"):
return np.where(den > 0, num / den * 100, 100)
def EMV(HIGH, LOW, VOL, N=14, M=9): # 简易波动指标
VOLUME = MA(VOL, N) / VOL
MID = 100 * (HIGH + LOW - REF(HIGH + LOW, 1)) / (HIGH + LOW)
EMV = MA(MID * VOLUME * (HIGH - LOW) / MA(HIGH - LOW, N), N)
MAEMV = MA(EMV, M)
return EMV, MAEMV
def DPO(CLOSE, M1=20, M2=10, M3=6): # 区间震荡线
DPO = CLOSE - REF(MA(CLOSE, M1), M2)
MADPO = MA(DPO, M3)
return DPO, MADPO
def BRAR(OPEN, CLOSE, HIGH, LOW, M1=26): # BRAR-ARBR 情绪指标
AR = SUM(HIGH - OPEN, M1) / SUM(OPEN - LOW, M1) * 100
BR = SUM(MAX(0, HIGH - REF(CLOSE, 1)), M1) / SUM(MAX(0, REF(CLOSE, 1) - LOW), M1) * 100
return AR, BR
def DFMA(CLOSE, N1=10, N2=50, M=10): # 平行线差指标
DIF = MA(CLOSE, N1) - MA(CLOSE, N2)
DIFMA = MA(DIF, M) # 通达信指标叫DMA 同花顺叫新DMA
return DIF, DIFMA
def MTM(CLOSE, N=12, M=6): # 动量指标
MTM = CLOSE - REF(CLOSE, N)
MTMMA = MA(MTM, M)
return MTM, MTMMA
def MASS(HIGH, LOW, N1=9, N2=25, M=6): # 梅斯线
MASS = SUM(MA(HIGH - LOW, N1) / MA(MA(HIGH - LOW, N1), N1), N2)
MA_MASS = MA(MASS, M)
return MASS, MA_MASS
def ROC(CLOSE, N=12, M=6): # 变动率指标
ROC = 100 * (CLOSE - REF(CLOSE, N)) / REF(CLOSE, N)
MAROC = MA(ROC, M)
return ROC, MAROC
def EXPMA(CLOSE, N1=12, N2=50): # EMA指数平均数指标
return EMA(CLOSE, N1), EMA(CLOSE, N2)
def OBV(CLOSE, VOL): # 能量潮指标
return SUM(IF(CLOSE > REF(CLOSE, 1), VOL, IF(CLOSE < REF(CLOSE, 1), -VOL, 0)), 0) / 10000
def MFI(CLOSE, HIGH, LOW, VOL, N=14): # MFI指标是成交量的RSI指标
TYP = (HIGH + LOW + CLOSE) / 3
pos_mf = SUM(IF(TYP > REF(TYP, 1), TYP * VOL, 0), N)
neg_mf = SUM(IF(TYP < REF(TYP, 1), TYP * VOL, 0), N)
with np.errstate(divide="ignore", invalid="ignore"):
V1 = np.where(neg_mf > 0, pos_mf / neg_mf, np.where(pos_mf > 0, np.inf, 0))
return 100 - (100 / (1 + V1))
def ASI(OPEN, CLOSE, HIGH, LOW, M1=26, M2=10): # 振动升降指标
LC = REF(CLOSE, 1)
AA = ABS(HIGH - LC)
BB = ABS(LOW - LC)
CC = ABS(HIGH - REF(LOW, 1))
DD = ABS(LC - REF(OPEN, 1))
R = IF(
(AA > BB) & (AA > CC),
AA + BB / 2 + DD / 4,
IF((BB > CC) & (BB > AA), BB + AA / 2 + DD / 4, CC + DD / 4),
)
X = CLOSE - LC + (CLOSE - OPEN) / 2 + LC - REF(OPEN, 1)
SI = 16 * X / R * MAX(AA, BB)
ASI = SUM(SI, M1)
ASIT = MA(ASI, M2)
return ASI, ASIT
def XSII(CLOSE, HIGH, LOW, N=102, M=7): # 薛斯通道II
AA = MA((2 * CLOSE + HIGH + LOW) / 4, 5) # 最新版DMA才支持 2021-12-4
TD1 = AA * N / 100
TD2 = AA * (200 - N) / 100
CC = ABS((2 * CLOSE + HIGH + LOW) / 4 - MA(CLOSE, 20)) / MA(CLOSE, 20)
DD = DMA(CLOSE, CC)
TD3 = (1 + M / 100) * DD
TD4 = (1 - M / 100) * DD
return TD1, TD2, TD3, TD4
def ZHUOYAO(CLOSE, N1=120, N2=60, N3=20, M=10): # 捉妖大师指标:中长短线趋势共振
LONG1 = (CLOSE / REF(CLOSE, N1) - 1) * 100 # 120日涨跌幅
LONG = EMA(LONG1, M) # 长线 EXPMA(长线1,10)
MID = (CLOSE / REF(CLOSE, N2) - 1) * 100 # 中线 60日涨跌幅
SHORT = (CLOSE / REF(CLOSE, N3) - 1) * 100 # 短线 20日涨跌幅
TREND = EMA(MID, M) # 趋势 EXPMA(中线,10)
return RD(LONG), RD(MID), RD(SHORT), RD(TREND)
def BIAS_SIGNAL(CLOSE, P=10, M=30): # 乖离率信号指标:M日乖离 + 短/长信号线趋势判断
X = (CLOSE - MA(CLOSE, M)) / MA(CLOSE, M) * 100 # M日乖离率
S_SMA = MA(X, P) # 短周期信号线 MA(X,P)
X_LMA = MA(X, M) # 长周期信号线 MA(X,M)
return RD(X), RD(S_SMA), RD(X_LMA)
def FK(CLOSE): # FK趋势指标:快线EMA(2)与斜率外推慢线EMA(42)比较
fast = EMA(CLOSE, 2)
slow = EMA(SLOPE(CLOSE, 21) * 20 + CLOSE, 42)
return fast > slow
def OUTPERFORM_20D(CLOSE, INDEX_CLOSE): # 20日相对强度:个股涨幅跑赢大盘返回1,否则返回0
stock_ret = (CLOSE - REF(CLOSE, 20)) / REF(CLOSE, 20)
index_ret = (INDEX_CLOSE - REF(INDEX_CLOSE, 20)) / REF(INDEX_CLOSE, 20)
return IF(stock_ret > index_ret, 1, 0)
def SAR(HIGH, LOW, AF_STEP=0.02, AF_MAX=0.2): # 抛物线转向指标:基于 ATR 思想的动态止损位
HIGH = np.asarray(HIGH, dtype=float)
LOW = np.asarray(LOW, dtype=float)
n = len(HIGH)
sar = np.full(n, np.nan)
if n == 0:
return sar
# 初始假设上涨趋势:SAR 起点取首根低点,极值点取首根高点
bull = True
af = AF_STEP
ep = HIGH[0]
sar[0] = LOW[0]
for i in range(1, n):
# 下一根 SAR = 前一根 SAR + AF * (EP - 前一根 SAR)
new_sar = sar[i - 1] + af * (ep - sar[i - 1])
# SAR 不能进入前两根 K 线极值范围(Wilder 标准限制,避免 SAR 被价格穿越)
prev2 = max(i - 2, 0)
if bull:
new_sar = min(new_sar, LOW[i - 1], LOW[prev2])
else:
new_sar = max(new_sar, HIGH[i - 1], HIGH[prev2])
sar[i] = new_sar
# 反转判断:上涨时 LOW 穿越止损位 / 下跌时 HIGH 穿越止损位
if bull and LOW[i] <= new_sar:
bull = False
sar[i] = ep # 反转点 SAR = 前极值点
ep = LOW[i]
af = AF_STEP
elif not bull and HIGH[i] >= new_sar:
bull = True
sar[i] = ep
ep = HIGH[i]
af = AF_STEP
else:
# 无反转,更新极值点和加速因子
if bull and HIGH[i] > ep:
ep = HIGH[i]
af = min(af + AF_STEP, AF_MAX)
elif not bull and LOW[i] < ep:
ep = LOW[i]
af = min(af + AF_STEP, AF_MAX)
return sar
def VWAP(CLOSE, HIGH, LOW, VOL, N=20): # 成交量加权均价:N日滚动机构基准成本价
TP = (HIGH + LOW + CLOSE) / 3.0 # 典型价格
num = pd.Series(TP * VOL).rolling(N).sum().values
den = pd.Series(VOL).rolling(N).sum().values
with np.errstate(divide="ignore", invalid="ignore"):
return np.where(den > 0, num / den, np.nan)
def AROON(HIGH, LOW, N=25): # 阿隆指标:趋势启动时机识别(N周期内新高/新低距今多少根)
# HHVBARS/LLVBARS 返回极值距今的周期数
up_bars = HHVBARS(HIGH, N) # N周期最高价距今周期数
down_bars = LLVBARS(LOW, N) # N周期最低价距今周期数
AROON_UP = (N - up_bars) / N * 100 # 越接近100=近期创新高=上涨动能强
AROON_DOWN = (N - down_bars) / N * 100 # 越接近100=近期创新低=下跌动能强
OSC = AROON_UP - AROON_DOWN # 震荡指标:正值多头,负值空头
return RD(AROON_UP), RD(AROON_DOWN), RD(OSC)
def FSL(CLOSE, VOL, CAPITAL): # 分水岭指标:多空趋势强弱分界(SWS含换手率动态平滑)
# SWL = (EMA(C,5)*7 + EMA(C,10)*3) / 10 : 5日/10日指数均值的加权合成
SWL = (EMA(CLOSE, 5) * 7 + EMA(CLOSE, 10) * 3) / 10
# SWS = DMA(EMA(C,12), MAX(1, 100*SUM(VOL,5)/(3*CAPITAL)))
# 平滑因子 = 5日成交量换手率放大值,CAPITAL 为流通股本
A = MAX(1, 100 * (SUM(VOL, 5) / (3 * CAPITAL)))
A = MIN(A, 1.0) # 模拟通达信 DMA(X,A) 内部钳制 A<=1,避免序列因子越界发散
SWS = DMA(EMA(CLOSE, 12), A)
return RD(SWL), RD(SWS)
# ── V4.3 新增:无未来函数指标(只引用当期及历史数据,信号不漂移) ────────────
def HMA(S, N=16): # Hull 赫尔均线:WMA(2*WMA(N/2)-WMA(N), sqrt(N)),低滞后且不重绘
return WMA(2 * WMA(S, N // 2) - WMA(S, N), max(int(round(np.sqrt(N))), 1))
def KAMA(S, N=10, FAST=2, SLOW=30): # 考夫曼自适应均线:效率比驱动平滑系数(贴价/走平自适应)
S = np.asarray(S, dtype=float)
change = np.abs(S - REF(S, N)) # N 周期净变化
volatility = SUM(np.abs(DIFF(S)), N) # N 周期路径总波动
with np.errstate(divide="ignore", invalid="ignore"):
er = np.where(volatility > 0, change / volatility, 0.0) # 效率比 0~1
er = np.where(np.isnan(change) | np.isnan(volatility), np.nan, er) # 预热段保持 NaN
fast_sc = 2.0 / (FAST + 1)
slow_sc = 2.0 / (SLOW + 1)
sc = (er * (fast_sc - slow_sc) + slow_sc) ** 2 # 平滑系数(ER 高→贴价,ER 低→走平)
out = np.full(len(S), np.nan)
valid = np.flatnonzero(~np.isnan(sc))
if len(valid):
start = valid[0]
out[start] = S[start]
for i in range(start + 1, len(S)):
out[i] = out[i - 1] + sc[i] * (S[i] - out[i - 1])
return out
def SUPERTREND(CLOSE, HIGH, LOW, N=10, M=3.0): # 超级趋势:HL2±M*ATR 递推锁带,穿带翻多空
CLOSE = np.asarray(CLOSE, dtype=float)
HIGH = np.asarray(HIGH, dtype=float)
LOW = np.asarray(LOW, dtype=float)
n = len(CLOSE)
sar_like = np.full(n, np.nan)
if n == 0:
return sar_like, np.zeros(0, dtype=int)
# Wilder 平滑 ATR(与 TradingView ta.rma 同口径);首根 TR 用自身 H-L
prev_c = np.concatenate(([CLOSE[0]], CLOSE[:-1]))
tr = MAX(MAX(HIGH - LOW, ABS(HIGH - prev_c)), ABS(LOW - prev_c))
atr = np.empty(n)
atr[0] = tr[0]
for i in range(1, n):
atr[i] = atr[i - 1] + (tr[i] - atr[i - 1]) / N
hl2 = (HIGH + LOW) / 2
basic_up = hl2 + M * atr
basic_dn = hl2 - M * atr
# 递推锁带:带只朝价格方向收紧,反向放宽被前收盘穿越时重置
final_up = basic_up.copy()
final_dn = basic_dn.copy()
for i in range(1, n):
final_up[i] = (
basic_up[i]
if (basic_up[i] < final_up[i - 1] or CLOSE[i - 1] > final_up[i - 1])
else final_up[i - 1]
)
final_dn[i] = (
basic_dn[i]
if (basic_dn[i] > final_dn[i - 1] or CLOSE[i - 1] < final_dn[i - 1])
else final_dn[i - 1]
)
direction = np.ones(n, dtype=int) # 1=多头(ST=下轨),-1=空头(ST=上轨)
for i in range(1, n):
if direction[i - 1] == 1:
direction[i] = -1 if CLOSE[i] < final_dn[i] else 1
else:
direction[i] = 1 if CLOSE[i] > final_up[i] else -1
st = np.where(direction == 1, final_dn, final_up)
return st, direction
def CHANDELIER(CLOSE, HIGH, LOW, N=22, M=22, K=3.0): # 吊灯止损:多头 HHV-K*ATR / 空头 LLV+K*ATR
atr = ATR(CLOSE, HIGH, LOW, M)
long_stop = HHV(HIGH, N) - atr * K
short_stop = LLV(LOW, N) + atr * K
return long_stop, short_stop
def ICHIMOKU(HIGH, LOW, CLOSE, P1=9, P2=26, P3=52, SHIFT=26): # 一目均衡表:五线一云
tenkan = (HHV(HIGH, P1) + LLV(LOW, P1)) / 2 # 转换线
kijun = (HHV(HIGH, P2) + LLV(LOW, P2)) / 2 # 基准线
span_a = REF((tenkan + kijun) / 2, SHIFT) # 先行带A:SHIFT期前的值画到当前(仅过去数据)
span_b = REF((HHV(HIGH, P3) + LLV(LOW, P3)) / 2, SHIFT) # 先行带B
chikou = REF(CLOSE, -SHIFT) # 迟行带:当前收盘画回SHIFT期前,仅作图示(当期信号禁用)
return tenkan, kijun, span_a, span_b, chikou
def UOS(CLOSE, HIGH, LOW, P1=7, P2=14, P3=28, M=6): # 终极指标:7/14/28 三周期 BP/TR 加权动量
th = MAX(HIGH, REF(CLOSE, 1)) # 真高
tl = MIN(LOW, REF(CLOSE, 1)) # 真低
bp = CLOSE - tl # 买压
tr = th - tl # 真波幅
def _avg(p): # SMA(BP,p)/SMA(TR,p),分母为零(一字段)时取中性 0.5
num, den = MA(bp, p), MA(tr, p)
with np.errstate(divide="ignore", invalid="ignore"):
ratio = np.where(den > 0, num / den, 0.5)
return np.where(np.isnan(num) | np.isnan(den), np.nan, ratio)
uos = 100 * (4 * _avg(P1) + 2 * _avg(P2) + _avg(P3)) / 7
uos_ma = MA(uos, M)
return RD(uos), RD(uos_ma)
def CMO(CLOSE, N=14): # 钱德动量振荡器:(涨幅和-跌幅和)/总波幅*100,比RSI更锐利对称
dif = DIFF(CLOSE)
up = SUM(MAX(dif, 0), N)
dn = SUM(MAX(-dif, 0), N)
total = up + dn
with np.errstate(divide="ignore", invalid="ignore"):
cmo = np.where(total > 0, (up - dn) / total * 100, 0.0)
return RD(np.where(np.isnan(total), np.nan, cmo))
def TSI(CLOSE, R=25, S=13, M=13): # 真实强度指数:动量双重EMA平滑归一(William Blau
m = DIFF(CLOSE)
num = EMA(EMA(m, R), S)
den = EMA(EMA(ABS(m), R), S)
with np.errstate(divide="ignore", invalid="ignore"):
tsi = np.where(den != 0, 100 * num / den, 0.0)
tsi = np.where(np.isnan(num) | np.isnan(den), np.nan, tsi)
return RD(tsi), RD(EMA(tsi, M))
def FISHER(HIGH, LOW, N=9): # 费雪变换(John Ehlers):区间位置正态化,拐点尖锐
price = (HIGH + LOW) / 2.0
highest = HHV(price, N)
lowest = LLV(price, N)
rng = highest - lowest
with np.errstate(divide="ignore", invalid="ignore"):
raw = np.where(rng > 0, 2.0 * (price - lowest) / rng - 1.0, 0.0) # 归一化到 -1~1
norm = np.where(np.isnan(rng), np.nan, raw)
n = len(price)
value = np.full(n, np.nan)
fisher = np.full(n, np.nan)
for i in range(n):
if np.isnan(norm[i]):
continue
prev_v = 0.0 if np.isnan(value[i - 1]) else value[i - 1]
prev_f = 0.0 if np.isnan(fisher[i - 1]) else fisher[i - 1]
v = 0.33 * norm[i] + 0.67 * prev_v
v = max(-0.999, min(0.999, v)) # 钳制避免 ln 发散
value[i] = v
fisher[i] = 0.5 * np.log((1 + v) / (1 - v)) + 0.5 * prev_f
return fisher, REF(fisher, 1) # 第二返回值为触发线(前一期值)
def SQUEEZE(CLOSE, HIGH, LOW, N=20, BB=2.0, KC=1.5): # TTM 挤压动量:布林收进肯特纳,回归动量定方向
mid = MA(CLOSE, N)
dev = STD(CLOSE, N) * BB
tr = MAX(MAX(HIGH - LOW, ABS(HIGH - REF(CLOSE, 1))), ABS(LOW - REF(CLOSE, 1)))
range_ma = MA(tr, N)
# 挤压判定:布林带(±dev)整体落在肯特纳通道(±KC×range_ma)内部(NaN 预热期比较为 False)
sqz_on = dev < KC * range_ma
# 动量:中价偏离 (HH+LL)/2 与 SMA(C,N) 的均值,取线性回归当前拟合值
delta = CLOSE - ((HHV(HIGH, N) + LLV(LOW, N)) / 2 + mid) / 2
mom = FORCAST(delta, N)
return sqz_on, RD(mom)
def CHOP(HIGH, LOW, CLOSE, N=14): # 盘整指数:>61.8 震荡 / <38.2 趋势(ΣTR/区间 对数缩放到 0~100)
tr = MAX(MAX(HIGH - LOW, ABS(HIGH - REF(CLOSE, 1))), ABS(LOW - REF(CLOSE, 1)))
tr_sum = SUM(tr, N)
rng = HHV(HIGH, N) - LLV(LOW, N)
with np.errstate(divide="ignore", invalid="ignore"):
ratio = np.where((tr_sum > 0) & (rng > 0), tr_sum / rng, np.nan)
return 100 * np.log10(ratio) / np.log10(N)
def AD(CLOSE, HIGH, LOW, VOL): # 累积/派发线(Marc Chaikin):CLV*VOL 累计,OBV 的精细化版
rng = HIGH - LOW
with np.errstate(divide="ignore", invalid="ignore"):
clv = np.where(rng > 0, ((CLOSE - LOW) - (HIGH - CLOSE)) / rng, 0.0) # 收盘位置 -1~1
return SUM(clv * VOL, 0)
def CMF(CLOSE, HIGH, LOW, VOL, N=20): # 佳庆资金流量:N日 CLV*VOL 之和 / N日成交量之和
rng = HIGH - LOW
with np.errstate(divide="ignore", invalid="ignore"):
clv = np.where(rng > 0, ((CLOSE - LOW) - (HIGH - CLOSE)) / rng, 0.0)
mfv_sum = SUM(clv * VOL, N)
vol_sum = SUM(VOL, N)
with np.errstate(divide="ignore", invalid="ignore"):
cmf = np.where(vol_sum > 0, mfv_sum / vol_sum, 0.0)
return np.where(np.isnan(vol_sum) | np.isnan(mfv_sum), np.nan, cmf)
def EFI(CLOSE, VOL, N=13): # 艾尔德强力指数:EMA(ΔC×V),同时融合方向/幅度/成交量三维
return EMA(DIFF(CLOSE) * VOL, N)
def BBP(CLOSE, N=20, P=2): # 布林位置 %B:收盘在带内位置百分比(0=下轨,50=中轨,100=上轨)
mid = MA(CLOSE, N)
dev = STD(CLOSE, N) * P
width = 2 * dev
with np.errstate(divide="ignore", invalid="ignore"):
pos = np.where(width > 0, (CLOSE - (mid - dev)) / width * 100, 50.0)
return RD(np.where(np.isnan(width), np.nan, pos))
def BBW(CLOSE, N=20, P=2): # 布林带宽:(上轨-下轨)/中轨*100,收窄=波动挤压/变盘预警
mid = MA(CLOSE, N)
dev = STD(CLOSE, N) * P
with np.errstate(divide="ignore", invalid="ignore"):
width = np.where(mid > 0, 2 * dev / mid * 100, np.nan)
return RD(width)
# 望大家能提交更多指标和函数 https://github.com/mpquant/MyTT