mootdx Python 个股分析实战:从数据获取到技术指标计算
title: “mootdx Python 个股分析实战:从数据获取到技术指标计算”
tags:
- Python
- mootdx
- 股票分析
- 量化投资
- 技术指标
categories: - Python
description: “用 Python + mootdx 免费获取 A 股个股行情数据,手把手实现 K 线获取、MACD/KDJ/RSI/BOLL 技术指标计算、均线金叉死叉信号检测,附完整可运行代码。”
mootdx Python 个股分析实战:从数据获取到技术指标计算
导读:这篇文章记录我用 mootdx 做 A 股个股分析的完整流程——从安装配置到拉数据,再到计算 MACD、KDJ、RSI、布林带这些常用技术指标。所有代码都可以直接复制运行,适合想用 Python 做股票分析但不想花钱买数据接口的同学。
一、为什么选 mootdx
做个股分析首先要解决数据来源问题。我试过几个方案:
- tushare:免费额度越来越少,很多接口要积分,新用户基本不够用
- akshare:接口多但文档比较乱,部分数据源不稳定
- baostock:免费好用,但只支持日线以上周期,分钟级数据拿不到
- mootdx:基于通达信协议,免费、实时、支持分钟线,还支持本地离线数据
mootdx 的最新版本是 0.11.7(2024 年 5 月发布),底层依赖 tdxpy,通过封装通达信 TCP 协议直接从通达信服务器拿数据。速度快、免费、数据准,日常个股分析绰绰有余。
⚠️ 版本说明:本文所有代码基于 mootdx 0.11.7,Python 3.8+。老版本(0.10.x 及之前)部分 API 参数有差异,建议升级到最新版。
二、安装和初始化
2.1 安装
pip install -U 'mootdx[all]'
[all] 会把所有可选依赖都装上,省得后面少这个缺那个。如果你网络环境一般,也可以只装核心:
pip install mootdx
2.2 选最快的服务器
mootdx 连的是通达信行情服务器,国内有几十个节点。第一次用建议先跑一下 bestip 测试:
python -m mootdx bestip -vv
加上 -vv 可以看到每个服务器的延迟。测完之后会自动写入配置文件,后面连接就用最快的那个节点。
2.3 验证安装
import mootdx
print(f"mootdx 版本: {mootdx.__version__}")
输出 0.11.7 就说明安装没问题。
三、获取个股行情数据
mootdx 有两种数据获取方式:在线行情(从通达信服务器拉)和本地数据(读通达信客户端的离线文件)。日常用在线行情就够了。
3.1 创建行情客户端
from mootdx.quotes import Quotes
client = Quotes.factory(market='std', bestip=True, heartbeat=True)
几个参数说明:
| 参数 | 说明 |
|---|---|
market='std' |
标准市场(A股)。期货期权用 'ext' |
bestip=True |
自动选择最快服务器 |
heartbeat=True |
保持心跳,长时间运行不会断连 |
multithread=True |
多线程模式,批量查询时开启 |
3.2 获取 K 线数据
client.bars() 是获取 K 线的主力方法:
df = client.bars(symbol='600519', frequency=9, offset=120)
print(df.tail(5))
frequency 参数控制 K 线周期:
| frequency 值 | 周期 |
|---|---|
| 9 | 日线 |
| 5 | 5 分钟线 |
| 6 | 15 分钟线 |
| 7 | 30 分钟线 |
| 8 | 60 分钟线 |
| 10 | 周线 |
| 11 | 月线 |
offset 是返回的数据条数,单次最多约 800 条。需要更多数据就得分批拉取。
返回的 DataFrame 列名如下:
open 开盘价
close 收盘价
high 最高价
low 最低价
vol 成交量(手)
amount 成交额
3.3 获取实时报价
quote = client.quotes(symbol='600519')
print(quote)
返回当前最新的买卖盘口、现价、涨跌幅等数据。symbol 也可以传列表,一次查多只:
quotes = client.quotes(symbol=['600519', '000858', '300750'])
3.4 获取分时数据
minute_data = client.minute(symbol='600519')
返回当天的分时走势数据。
💡 我的习惯:日常分析用日线
frequency=9+offset=120拿最近半年数据。做短线的时候切换到 5 分钟线frequency=5。
四、计算技术指标
拿到数据之后,就可以开始算技术指标了。我这里不用 TA-Lib(安装太折腾),直接用 pandas 和 numpy 手写,逻辑更清晰。
4.1 封装一个数据获取函数
先把数据拉取和预处理封装起来,后面所有指标计算都基于这个函数:
import pandas as pd
import numpy as np
from mootdx.quotes import Quotes
client = Quotes.factory(market='std', bestip=True, heartbeat=True)
def get_stock_data(symbol, days=120):
df = client.bars(symbol=symbol, frequency=9, offset=days)
if df is None or df.empty:
print(f"未获取到 {symbol} 的数据")
return None
df = df.copy()
df.index = range(len(df))
return df
df = get_stock_data('600519', 120)
print(df.tail(3))
4.2 均线(MA)
均线是最基础的趋势指标,5 日线和 20 日线的金叉死叉是很多人看趋势的第一步:
def calc_ma(df, periods=[5, 10, 20, 60]):
for p in periods:
df[f'MA{p}'] = df['close'].rolling(window=p).mean()
return df
df = calc_ma(df)
print(df[['close', 'MA5', 'MA20']].tail(5))
检测金叉死叉信号:
def detect_cross(df):
df['MA5_prev'] = df['MA5'].shift(1)
df['MA20_prev'] = df['MA20'].shift(1)
df['golden_cross'] = (df['MA5_prev'] <= df['MA20_prev']) & (df['MA5'] > df['MA20'])
df['death_cross'] = (df['MA5_prev'] >= df['MA20_prev']) & (df['MA5'] < df['MA20'])
return df
df = detect_cross(df)
crosses = df[df['golden_cross'] | df['death_cross']]
print(f"最近一次金叉/死叉: {crosses[['close', 'golden_cross', 'death_cross']].tail(1)}")
4.3 MACD
MACD 是趋势类指标里用得最多的,参数一般用默认的 12/26/9:
def calc_macd(df, fast=12, slow=26, signal=9):
ema_fast = df['close'].ewm(span=fast, adjust=False).mean()
ema_slow = df['close'].ewm(span=slow, adjust=False).mean()
df['DIF'] = ema_fast - ema_slow
df['DEA'] = df['DIF'].ewm(span=signal, adjust=False).mean()
df['MACD'] = 2 * (df['DIF'] - df['DEA'])
return df
df = calc_macd(df)
print(df[['close', 'DIF', 'DEA', 'MACD']].tail(5))
检测 MACD 金叉(DIF 上穿 DEA):
df['DIF_prev'] = df['DIF'].shift(1)
df['DEA_prev'] = df['DEA'].shift(1)
df['macd_cross'] = (df['DIF_prev'] <= df['DEA_prev']) & (df['DIF'] > df['DEA'])
macd_signals = df[df['macd_cross']]
if not macd_signals.empty:
print(f"MACD 最近一次金叉在倒数第 {len(df) - macd_signals.index[-1]} 根K线")
else:
print("近 120 个交易日内无 MACD 金叉")
4.4 KDJ
KDJ 是震荡类指标,短线交易者用得很多。参数通常用 9/3/3:
def calc_kdj(df, n=9, m1=3, m2=3):
low_min = df['low'].rolling(window=n).min()
high_max = df['high'].rolling(window=n).max()
rsv = (df['close'] - low_min) / (high_max - low_min) * 100
K = np.zeros(len(df))
D = np.zeros(len(df))
K[0] = 50
D[0] = 50
for i in range(1, len(df)):
K[i] = (2/3) * K[i-1] + (1/3) * rsv.iloc[i]
D[i] = (2/3) * D[i-1] + (1/3) * K[i]
df['K'] = K
df['D'] = D
df['J'] = 3 * K - 2 * D
return df
df = calc_kdj(df)
print(df[['close', 'K', 'D', 'J']].tail(5))
判断超买超卖:
last = df.iloc[-1]
if last['K'] > 80 and last['D'] > 80:
print("KDJ 超买区域,注意回调风险")
elif last['K'] < 20 and last['D'] < 20:
print("KDJ 超卖区域,关注反弹机会")
else:
print(f"KDJ 当前: K={last['K']:.1f}, D={last['D']:.1f}, J={last['J']:.1f}")
4.5 RSI
RSI(相对强弱指标)也是判断超买超卖的经典指标:
def calc_rsi(df, periods=[6, 12, 24]):
for period in periods:
delta = df['close'].diff()
gain = delta.where(delta > 0, 0)
loss = (-delta).where(delta < 0, 0)
avg_gain = gain.rolling(window=period).mean()
avg_loss = loss.rolling(window=period).mean()
rs = avg_gain / avg_loss
df[f'RSI{period}'] = 100 - (100 / (1 + rs))
return df
df = calc_rsi(df)
print(df[['close', 'RSI6', 'RSI12', 'RSI24']].tail(5))
RSI 的判断逻辑:
| RSI 值 | 含义 |
|---|---|
| > 70 | 超买,短期可能回调 |
| 30-70 | 正常区间 |
| < 30 | 超卖,短期可能反弹 |
4.6 布林带(BOLL)
布林带用来判断波动率和支撑压力位:
def calc_boll(df, n=20, k=2):
df['BOLL_MID'] = df['close'].rolling(window=n).mean()
std = df['close'].rolling(window=n).std()
df['BOLL_UP'] = df['BOLL_MID'] + k * std
df['BOLL_DN'] = df['BOLL_MID'] - k * std
return df
df = calc_boll(df)
print(df[['close', 'BOLL_UP', 'BOLL_MID', 'BOLL_DN']].tail(5))
价格突破上轨可能要回调,跌破下轨可能要反弹。但实际交易中布林带的用法远比这个复杂,不能简单照搬。
五、整合成一个分析函数
把上面的指标计算整合到一起,输入股票代码直接出分析报告:
def analyze_stock(symbol, days=120):
df = get_stock_data(symbol, days)
if df is None:
return
df = calc_ma(df)
df = calc_macd(df)
df = calc_kdj(df)
df = calc_rsi(df)
df = calc_boll(df)
last = df.iloc[-1]
prev = df.iloc[-2]
print(f"{'='*50}")
print(f"个股分析: {symbol}")
print(f"最新收盘价: {last['close']:.2f}")
print(f"涨跌幅: {(last['close'] - prev['close']) / prev['close'] * 100:.2f}%")
print(f"{'='*50}")
print(f"\n【均线系统】")
print(f" MA5={last['MA5']:.2f} MA10={last['MA10']:.2f} MA20={last['MA20']:.2f} MA60={last['MA60']:.2f}")
if last['close'] > last['MA20']:
print(f" 收盘价站上20日均线,中期趋势偏多")
else:
print(f" 收盘价跌破20日均线,中期趋势偏弱")
print(f"\n【MACD】")
print(f" DIF={last['DIF']:.3f} DEA={last['DEA']:.3f} MACD={last['MACD']:.3f}")
if last['MACD'] > 0:
print(f" MACD 柱状线为正,多头动能")
else:
print(f" MACD 柱状线为负,空头动能")
print(f"\n【KDJ】")
print(f" K={last['K']:.1f} D={last['D']:.1f} J={last['J']:.1f}")
if last['K'] > 80 and last['D'] > 80:
print(f" ⚠️ 超买区域,注意回调风险")
elif last['K'] < 20 and last['D'] < 20:
print(f" ⚠️ 超卖区域,关注反弹机会")
print(f"\n【RSI】")
print(f" RSI6={last['RSI6']:.1f} RSI12={last['RSI12']:.1f} RSI24={last['RSI24']:.1f}")
print(f"\n【布林带】")
print(f" 上轨={last['BOLL_UP']:.2f} 中轨={last['BOLL_MID']:.2f} 下轨={last['BOLL_DN']:.2f}")
if last['close'] > last['BOLL_UP']:
print(f" ⚠️ 价格突破上轨,短期可能超买")
elif last['close'] < last['BOLL_DN']:
print(f" ⚠️ 价格跌破下轨,短期可能超卖")
return df
df = analyze_stock('600519')
运行结果大概长这样:
==================================================
个股分析: 600519
最新收盘价: 1756.00
涨跌幅: -0.85%
==================================================
【均线系统】
MA5=1762.40 MA10=1768.30 MA20=1755.60 MA60=1720.50
收盘价站上20日均线,中期趋势偏多
【MACD】
DIF=5.230 DEA=8.120 MACD=-5.780
MACD 柱状线为负,空头动能
【KDJ】
K=65.3 D=72.1 J=51.7
【RSI】
RSI6=42.3 RSI12=48.7 RSI24=55.1
【布林带】
上轨=1810.50 中轨=1755.60 下轨=1700.70
六、批量筛选:找均线多头排列的股票
单个分析看完了,再举一个批量筛选的例子——从自选股列表里找出均线多头排列(MA5 > MA10 > MA20 > MA60)的股票:
def screen_bullish(stock_list):
results = []
for symbol in stock_list:
try:
df = get_stock_data(symbol, 120)
if df is None or len(df) < 60:
continue
df = calc_ma(df)
last = df.iloc[-1]
if last['MA5'] > last['MA10'] > last['MA20'] > last['MA60']:
results.append({
'代码': symbol,
'收盘价': last['close'],
'MA5': round(last['MA5'], 2),
'MA20': round(last['MA20'], 2),
'MA60': round(last['MA60'], 2),
})
except Exception as e:
print(f"{symbol} 查询失败: {e}")
continue
return pd.DataFrame(results)
watchlist = ['600519', '000858', '300750', '601318', '000001', '600036']
result = screen_bullish(watchlist)
print(result)
⚠️ 注意:批量查询时建议加个
time.sleep(0.5)做间隔,请求太频繁可能被服务器断连。mootdx 有断线重连机制,但控频总归是更稳妥的做法。
七、踩过的坑
1. 返回数据是 DataFrame 不是字典
我刚开始用的时候老把 client.bars() 的返回值当字典处理,结果各种报错。mootdx 返回的是 pandas DataFrame,得用 DataFrame 的方式操作。
2. frequency 参数不是字面意思
frequency=9 是日线,不是 9 天。这个编码是通达信协议的历史遗留,mootdx 只是沿用了。具体的对应关系上面第三节的表格里有。
3. 单次数据量有上限
offset 最大约 800 条。如果你要拉几年的日线数据,得分批请求。分钟线的数据量更大,更要注意这个限制。
4. 本地数据需要通达信客户端
如果你用 Reader 读取本地数据,前提是电脑上装了通达信客户端,而且数据目录要配置对。Windows 一般在 C:/new_tdx/vipdoc,macOS 在 /Applications/通达信.app/Contents/VIPDOC。
八、速查表
| 操作 | 代码 |
|---|---|
| 安装 | pip install -U 'mootdx[all]' |
| 测速选服务器 | python -m mootdx bestip -vv |
| 创建客户端 | Quotes.factory(market='std', bestip=True) |
| 日 K 线 | client.bars(symbol='600519', frequency=9, offset=120) |
| 5 分钟线 | client.bars(symbol='600519', frequency=5, offset=100) |
| 实时报价 | client.quotes(symbol='600519') |
| 分时数据 | client.minute(symbol='600519') |
| 周线 | client.bars(symbol='600519', frequency=10, offset=52) |
| 指数 K 线 | client.index(symbol='000001', frequency=9) |
| 读取本地日线 | Reader.factory(market='std', tdxdir='路径').daily(symbol) |
| 关闭连接 | client.close() |
九、常见问题
Q:macOS 上安装报错怎么办?
M1/M2 芯片的 Mac 试试用 Rosetta 模式安装:arch -x86_64 pip install mootdx。如果报 mini_racer 相关错误,单独装一下:pip install mini-racer。
Q:连接超时怎么处理?
先跑 python -m mootdx bestip -vv 重新测速。如果所有服务器都超时,检查网络环境——公司网络可能屏蔽了通达信的端口。
Q:返回的数据和通达信客户端看到的不一致?
确认 frequency 参数是否正确。另外通达信的数据分前复权和后复权,mootdx 默认返回不复权数据,做回测时需要自己处理复权。
Q:可以用来做自动交易吗?
mootdx 只提供数据读取,没有交易接口。自动交易需要对接券商的 API,这是另一套东西了。
Q:和 tushare 比哪个好?
各有优劣。tushare 的财务数据更全(但需要积分),mootdx 的行情数据更快更稳定。我的做法是行情数据用 mootdx,财务数据用 akshare。
参考链接
- mootdx 官方文档:https://mootdx.readthedocs.io/
- mootdx GitHub 仓库:https://github.com/mootdx/mootdx
- mootdx PyPI 页面:https://pypi.org/project/mootdx/
- MyTT 技术指标库(通达信指标 Python 移植):https://github.com/mpquant/MyTT
写了这么多,如果对你有帮助的话,给我点个赞 👍 收个藏 📌 吧~
如果你在用 Python 做股票分析的过程中遇到了什么问题,或者有更好的数据处理方案,欢迎在评论区聊聊。量化这条路坑挺多的,大家多交流才能少踩坑。
更多推荐



所有评论(0)