Files

238 lines
5.1 KiB
Python
Raw Permalink 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
# -*- 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
}