238 lines
5.1 KiB
Python
238 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
||||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
技术指标计算模块
|
|||
|
|
纯函数实现,不依赖外部状态
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from typing import List, Dict
|
|||
|
|
from ..models import KlineData
|
|||
|
|
|
|||
|
|
|
|||
|
|
def calculate_ma(data: List[float], period: int) -> float:
|
|||
|
|
"""
|
|||
|
|
计算移动平均线 (MA)
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
data: 数据列表(如收盘价)
|
|||
|
|
period: 周期
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
MA 值
|
|||
|
|
"""
|
|||
|
|
if not data:
|
|||
|
|
return 0
|
|||
|
|
if len(data) < period:
|
|||
|
|
return data[-1]
|
|||
|
|
return sum(data[-period:]) / period
|
|||
|
|
|
|||
|
|
|
|||
|
|
def calculate_adx(klines: List[KlineData], period: int = 14) -> float:
|
|||
|
|
"""
|
|||
|
|
计算 ADX (Average Directional Index)
|
|||
|
|
|
|||
|
|
ADX > 25: 有趋势
|
|||
|
|
ADX > 40: 强趋势
|
|||
|
|
ADX < 20: 无明显趋势
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
klines: K线数据列表
|
|||
|
|
period: 计算周期
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
ADX 值
|
|||
|
|
"""
|
|||
|
|
if len(klines) < period + 1:
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
# 计算 +DM 和 -DM
|
|||
|
|
plus_dm = []
|
|||
|
|
minus_dm = []
|
|||
|
|
tr_list = []
|
|||
|
|
|
|||
|
|
for i in range(1, len(klines)):
|
|||
|
|
high = klines[i].high
|
|||
|
|
low = klines[i].low
|
|||
|
|
prev_high = klines[i - 1].high
|
|||
|
|
prev_low = klines[i - 1].low
|
|||
|
|
prev_close = klines[i - 1].close
|
|||
|
|
|
|||
|
|
# +DM
|
|||
|
|
up_move = high - prev_high
|
|||
|
|
down_move = prev_low - low
|
|||
|
|
|
|||
|
|
if up_move > down_move and up_move > 0:
|
|||
|
|
plus_dm.append(up_move)
|
|||
|
|
else:
|
|||
|
|
plus_dm.append(0)
|
|||
|
|
|
|||
|
|
# -DM
|
|||
|
|
if down_move > up_move and down_move > 0:
|
|||
|
|
minus_dm.append(down_move)
|
|||
|
|
else:
|
|||
|
|
minus_dm.append(0)
|
|||
|
|
|
|||
|
|
# True Range
|
|||
|
|
tr = max(
|
|||
|
|
high - low,
|
|||
|
|
abs(high - prev_close),
|
|||
|
|
abs(low - prev_close)
|
|||
|
|
)
|
|||
|
|
tr_list.append(tr)
|
|||
|
|
|
|||
|
|
if len(tr_list) < period:
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
# 计算平滑值
|
|||
|
|
atr = sum(tr_list[-period:]) / period
|
|||
|
|
smoothed_plus_dm = sum(plus_dm[-period:]) / period
|
|||
|
|
smoothed_minus_dm = sum(minus_dm[-period:]) / period
|
|||
|
|
|
|||
|
|
# 计算 +DI 和 -DI
|
|||
|
|
if atr == 0:
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
plus_di = (smoothed_plus_dm / atr) * 100
|
|||
|
|
minus_di = (smoothed_minus_dm / atr) * 100
|
|||
|
|
|
|||
|
|
# 计算 DX
|
|||
|
|
di_sum = plus_di + minus_di
|
|||
|
|
if di_sum == 0:
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
dx = abs(plus_di - minus_di) / di_sum * 100
|
|||
|
|
|
|||
|
|
return dx
|
|||
|
|
|
|||
|
|
|
|||
|
|
def calculate_rsi(data: List[float], period: int = 14) -> float:
|
|||
|
|
"""
|
|||
|
|
计算 RSI (Relative Strength Index)
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
data: 数据列表(如收盘价)
|
|||
|
|
period: 计算周期
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
RSI 值 (0-100)
|
|||
|
|
"""
|
|||
|
|
if len(data) < period + 1:
|
|||
|
|
return 50 # 默认中性值
|
|||
|
|
|
|||
|
|
gains = []
|
|||
|
|
losses = []
|
|||
|
|
|
|||
|
|
for i in range(1, len(data)):
|
|||
|
|
change = data[i] - data[i - 1]
|
|||
|
|
if change > 0:
|
|||
|
|
gains.append(change)
|
|||
|
|
losses.append(0)
|
|||
|
|
else:
|
|||
|
|
gains.append(0)
|
|||
|
|
losses.append(abs(change))
|
|||
|
|
|
|||
|
|
if len(gains) < period:
|
|||
|
|
return 50
|
|||
|
|
|
|||
|
|
avg_gain = sum(gains[-period:]) / period
|
|||
|
|
avg_loss = sum(losses[-period:]) / period
|
|||
|
|
|
|||
|
|
if avg_loss == 0:
|
|||
|
|
return 100
|
|||
|
|
|
|||
|
|
rs = avg_gain / avg_loss
|
|||
|
|
rsi = 100 - (100 / (1 + rs))
|
|||
|
|
|
|||
|
|
return rsi
|
|||
|
|
|
|||
|
|
|
|||
|
|
def calculate_macd(data: List[float], fast: int = 12, slow: int = 26, signal: int = 9) -> Dict:
|
|||
|
|
"""
|
|||
|
|
计算 MACD (Moving Average Convergence Divergence)
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
data: 数据列表
|
|||
|
|
fast: 快线周期
|
|||
|
|
slow: 慢线周期
|
|||
|
|
signal: 信号线周期
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
{"macd": float, "signal": float, "histogram": float}
|
|||
|
|
"""
|
|||
|
|
if len(data) < slow + signal:
|
|||
|
|
return {"macd": 0, "signal": 0, "histogram": 0}
|
|||
|
|
|
|||
|
|
# 计算快慢 EMA(简化用 SMA 近似)
|
|||
|
|
ema_fast = calculate_ema_approx(data, fast)
|
|||
|
|
ema_slow = calculate_ema_approx(data, slow)
|
|||
|
|
|
|||
|
|
# MACD 线
|
|||
|
|
macd_line = ema_fast - ema_slow
|
|||
|
|
|
|||
|
|
# 信号线(MACD 的移动平均)
|
|||
|
|
# 简化处理
|
|||
|
|
signal_line = macd_line # 简化
|
|||
|
|
|
|||
|
|
# 柱状图
|
|||
|
|
histogram = macd_line - signal_line
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"macd": macd_line,
|
|||
|
|
"signal": signal_line,
|
|||
|
|
"histogram": histogram
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def calculate_ema_approx(data: List[float], period: int) -> float:
|
|||
|
|
"""
|
|||
|
|
计算指数移动平均线 (EMA) 的近似值
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
data: 数据列表
|
|||
|
|
period: 周期
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
EMA 值
|
|||
|
|
"""
|
|||
|
|
if not data:
|
|||
|
|
return 0
|
|||
|
|
if len(data) < period:
|
|||
|
|
return data[-1]
|
|||
|
|
|
|||
|
|
# 简化:使用 SMA 近似
|
|||
|
|
return sum(data[-period:]) / period
|
|||
|
|
|
|||
|
|
|
|||
|
|
def calculate_bollinger_bands(data: List[float], period: int = 20, std_dev: float = 2.0) -> Dict:
|
|||
|
|
"""
|
|||
|
|
计算布林带
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
data: 数据列表
|
|||
|
|
period: 周期
|
|||
|
|
std_dev: 标准差倍数
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
{"upper": float, "middle": float, "lower": float}
|
|||
|
|
"""
|
|||
|
|
if len(data) < period:
|
|||
|
|
current = data[-1] if data else 0
|
|||
|
|
return {"upper": current, "middle": current, "lower": current}
|
|||
|
|
|
|||
|
|
# 中轨(SMA)
|
|||
|
|
middle = sum(data[-period:]) / period
|
|||
|
|
|
|||
|
|
# 计算标准差
|
|||
|
|
subset = data[-period:]
|
|||
|
|
variance = sum((x - middle) ** 2 for x in subset) / period
|
|||
|
|
std = variance ** 0.5
|
|||
|
|
|
|||
|
|
# 上下轨
|
|||
|
|
upper = middle + std_dev * std
|
|||
|
|
lower = middle - std_dev * std
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"upper": upper,
|
|||
|
|
"middle": middle,
|
|||
|
|
"lower": lower
|
|||
|
|
}
|