前几天有个粉丝问我

"能 能 能 必须能"
今天这篇文章就教大家如何实现这一功能。
1、行情数据准备
通达信软件在实现条件选股的时候需要下载历史行情数据,同样的Python想要实现这一功能也需要把行情下载下来。
行情数据下载推荐大家使用xtquant+miniQMT,有人说还可以通过Tushare、AKShare、Pytdx库来下载行情,个人是不推荐的。 Tushare免费的接口额度少的可怜,要想下载全部历史行情还得花钱买积分,一年就是大几千。 AKShare是真的好,数据丰富,免费,但是频繁访问会导致IP被封,接口稳定性差。 Pytdx复权数据不好处理,还得自己根据分红配股的数据来算,增加了量化负担。
接下来回归正题,要下行情数据,首先要通过xtquant 的xtdata.download_history_data2
方法把历史行情下载到本地,然后通过xtdata.get_local_data
的方便把本地的行情保存成我们需要的行情文件。
这里我们直接把行情读取到pandas的Dataframe里然后通过to_feather
的方式保存成本地feather格式文件。
我直接把他们都存放到了项目的根目录data/1d
文件夹中了。
2、技术指标计算
这里我们用ta
库,它比ta-lib
安装容易,速度相对慢一些,但是对于我们普通人是完全够用的。 安装方式
pip install ta
这几帮大家列举一些常见的技术指标是如何计算的
假如我们已经把行情数据存放到
data/1d
目录下了
读取数据
python
import ta
import pandas as pd
import os
data_dir =r'data/1d' #替换成你实际的目录
file_paht = os.path.join(data_dir,'000001.SZ.feather')
df = pd.read_feather(file_paht)
print一下df的值可以得到如下结果:
yaml
code date open high low close preClose volume amount
2025-01-02 000001.SZ 2025-01-02 11.373652 11.412437 11.043981 11.082766 11.344563 1819597 2.102923e+09
2025-01-03 000001.SZ 2025-01-03 11.092462 11.189424 11.014892 11.034285 11.082766 1154680 1.320521e+09
2025-01-06 000001.SZ 2025-01-06 11.034285 11.131247 10.879145 11.092462 11.034285 1085536 1.234306e+09
2025-01-07 000001.SZ 2025-01-07 11.073070 11.179728 11.024589 11.160336 11.092462 747863 8.583290e+08
2025-01-08 000001.SZ 2025-01-08 11.150639 11.276690 11.053677 11.150639 11.160336 1062386 1.223599e+09
... ... ... ... ... ... ... ... ... ...
2025-08-15 000001.SZ 2025-08-15 12.230000 12.230000 11.940000 12.080000 12.200000 1948503 2.344073e+09
2025-08-18 000001.SZ 2025-08-18 12.060000 12.150000 12.030000 12.080000 12.080000 1239058 1.496527e+09
2025-08-19 000001.SZ 2025-08-19 12.100000 12.120000 12.020000 12.060000 12.080000 892860 1.077380e+09
2025-08-20 000001.SZ 2025-08-20 12.060000 12.120000 11.980000 12.070000 12.060000 1048739 1.265436e+09
2025-08-21 000001.SZ 2025-08-21 12.050000 12.190000 12.030000 12.150000 12.070000 970945 1.177635e+09
[155 rows x 9 columns]
接下来开始计算指标
python
from ta.trend import SMAIndicator
from ta.trend import EMAIndicator
from ta.trend import MACD
from ta.momentum import RSIIndicator
from ta.momentum import ROCIndicator
from ta.volatility import BollingerBands
from ta.volatility import AverageTrueRange
from ta.volume import OnBalanceVolumeIndicator
from ta.others import DailyLogReturnIndicator
data_dir =r'H:\huajie_python\星球\实操策略\data\1d'
file_paht = os.path.join(data_dir,'000001.SZ.feather')
df = pd.read_feather(file_paht)
# ========== 趋势指标 ==========
# 移动平均线 (SMA, EMA)
df['sma_20'] = SMAIndicator(df['close'],window=20).sma_indicator()
df['ema_20'] = EMAIndicator(df['close'],window=20).ema_indicator()
# MACD
macd = MACD(df['close'],window_slow=26,window_fast=12,window_sign=9)
df['macd'] = macd.macd()
df['macd_signal'] = macd.macd_signal()
df['macd_diff'] = macd.macd_diff()
# ========== 动量指标 ==========
# RSI
df['rsi'] = RSIIndicator(df['close'],window=14).rsi()
# ROC (Rate of Change)
df['roc'] = ROCIndicator(df['close'], window=12).roc()
# ========== 波动率指标 ==========
# 布林带
boll = BollingerBands(df['close'], window=20, window_dev=2)
df['boll_mavg'] = boll.bollinger_mavg()
df['boll_hband'] = boll.bollinger_hband()
df['boll_lband'] = boll.bollinger_lband()
# ATR
df['atr'] = AverageTrueRange(df['high'], df['low'], df['close'], window=14).average_true_range()
# ========== 成交量指标 ==========
# obv
df['obv'] = OnBalanceVolumeIndicator(df['close'],df['volume']).on_balance_volume()
# ========== 其它指标 ==========
# 对数收益率
df['dlr'] = DailyLogReturnIndicator(df['close']).daily_log_return()
这是计算结果

画重点
ta库把技术指标分成了4大类:趋势指标(Trend indicators)、动量指标(Momentum indicators)、波动率指标(Volatility indicators)和成交量指标(Volume indicators)。
可以通过from ta import trend,momentum,volatility,volume
来调用。上面的示例是推荐的写法,当然你可以可以按照下面的写法,但是这样写没有智能提示
python
# 推荐写法,智能提示友好
from ta.trend import SMAIndicator
df['sma_20'] = SMAIndicator(df['close'],window=20).sma_indicator()
# 不推荐写法
df['sma_20'] = ta.trend.sma_indicator(df['Close'], window=20)
ta库还有一个比较方便的用法就是一次性计算出所有的技术指标或者按照类别一次性计算
python
# 一次性计算出所有的指标
from ta import add_all_ta_features
file_paht = os.path.join(data_dir,'000001.SZ.feather')
df = pd.read_feather(file_paht)
add_all_ta_features(df,'open','high','low','close','volume')
# 按类型一次性计算所有指标
from ta import add_momentum_ta,add_trend_ta,add_volume_ta,add_volatility_ta
# 计算出所有趋势指标
file_paht = os.path.join(data_dir,'000001.SZ.feather')
df = pd.read_feather(file_paht)
add_trend_ta(df,'high','low','close')
如果你对指标计算时的窗口没有要求,那直接用add_all_ta_features
或者add_momentum_ta,add_trend_ta,add_volume_ta,add_volatility_ta
是最方便的了,但是如果你需要特定的窗口比如计算下13天的均线指标,那还是得用下面这种方法
python
df['sma_13'] = SMAIndicator(df['close'],window=13).sma_indicator()
不知道Ta库有哪些指标的,可以直接问AI,很方便,AI也能帮你直接把代码写出来,当然也可以看官方文档https://technical-analysis-library-in-python.readthedocs.io/en/latest/index.html
,或者直接看源码,很清晰的就能知道ta支持哪些技术指标
3、技术指标选股
有了技术指标,我们就可以基于这个来选股了,方法也很简单,基本上都是基于Dataframe格式数据的一个筛选操作。
接下来花姐用几个策略教大家演示如何根据技术指标选股。
策略1:趋势 + 放量确认(MA50>MA200 且 近20日放量)
逻辑:
- SMA(50) > SMA(200),且收盘 > SMA(50)
- 近20日均量 > 近60日均量 * 1.2(量能确认)
- 近60日最大回撤 > -25%(风险兜底)
python
import numpy as np
import pandas as pd
import os
# ta 库的指标
from ta.trend import SMAIndicator
def strat1_trend_volume(df: pd.DataFrame):
close, volume = df['close'], df['volume']
ma50 = SMAIndicator(close, window=50).sma_indicator()
ma200 = SMAIndicator(close, window=200).sma_indicator()
vol20 = volume.rolling(20, min_periods=20).mean()
vol60 = volume.rolling(60, min_periods=60).mean()
if len(df) < 200 or np.isnan(ma50.iloc[-1]) or np.isnan(ma200.iloc[-1]):
return False, {}
cond_trend = (ma50.iloc[-1] > ma200.iloc[-1]) and (close.iloc[-1] > ma50.iloc[-1])
cond_vol = (not np.isnan(vol20.iloc[-1]) and not np.isnan(vol60.iloc[-1]) and vol20.iloc[-1] > vol60.iloc[-1] * 1.2)
mdd_60 = recent_max_drawdown(close, 60)
keep = bool(cond_trend and cond_vol and (mdd_60 > -0.25))
metrics = {
"ma50": float(ma50.iloc[-1]),
"ma200": float(ma200.iloc[-1]),
"volume_surge": float(vol20.iloc[-1] / max(vol60.iloc[-1], 1e-6)) if (not np.isnan(vol20.iloc[-1]) and not np.isnan(vol60.iloc[-1])) else np.nan,
"mdd_60": float(mdd_60),
"score": float((ma50.iloc[-1] / max(ma200.iloc[-1], 1e-6)) * (vol20.iloc[-1] / max(vol60.iloc[-1], 1e-6)) if keep else 0.0)
}
return keep, metrics
def recent_max_drawdown(close: pd.Series, lookback: int = 60) -> float:
s = close.tail(lookback)
return max_drawdown(s)
def max_drawdown(series: pd.Series) -> float:
# 针对收盘价序列计算最大回撤
if len(series) < 2:
return 0.0
cummax = series.cummax()
dd = series / cummax - 1.0
return float(dd.min())
data_dir =r'data/1d'
file_paht = os.path.join(data_dir,'000001.SZ.feather')
df = pd.read_feather(file_paht)
keep, metrics =strat1_trend_volume(df)
print(keep,metrics)
策略2:动量 + 波动率收敛 + 回撤受控 逻辑:
- 126日动量 > +30%
- ATR(20)/收盘 < ATR(60)/收盘 * 0.8(用 ATR% 衡量收敛)
- 近60日最大回撤 > -15%
python
import numpy as np
import pandas as pd
import os
# ta 库的指标
from ta.volatility import AverageTrueRange
def strat2_momentum_vol_calmdd(df: pd.DataFrame):
close, high, low = df['close'], df['high'], df['low']
if len(df) < 126:
return False, {}
mom_126 = close.iloc[-1] / close.iloc[-126] - 1.0
atr20 = AverageTrueRange(high, low, close, window=20).average_true_range()
atr60 = AverageTrueRange(high, low, close, window=60).average_true_range()
# 归一到价格得到 ATR 百分比
atrp20 = atr20 / close
atrp60 = atr60 / close
if np.isnan(atrp20.iloc[-1]) or np.isnan(atrp60.iloc[-1]):
return False, {}
cond_mom = mom_126 > 0.30
cond_vol = atrp20.iloc[-1] < atrp60.iloc[-1] * 0.8
mdd_60 = recent_max_drawdown(close, 60)
cond_dd = mdd_60 > -0.15
keep = bool(cond_mom and cond_vol and cond_dd)
metrics = {
"mom_126": float(mom_126),
"atrp20": float(atrp20.iloc[-1]),
"atrp60": float(atrp60.iloc[-1]),
"vol_ratio": float(atrp20.iloc[-1] / max(atrp60.iloc[-1], 1e-6)),
"mdd_60": float(mdd_60),
"score": float(max(0.0, mom_126) * (atrp60.iloc[-1] / max(atrp20.iloc[-1], 1e-6)) if keep else 0.0)
}
return keep, metrics
def recent_max_drawdown(close: pd.Series, lookback: int = 60) -> float:
s = close.tail(lookback)
return max_drawdown(s)
def max_drawdown(series: pd.Series) -> float:
# 针对收盘价序列计算最大回撤
if len(series) < 2:
return 0.0
cummax = series.cummax()
dd = series / cummax - 1.0
return float(dd.min())
data_dir =r'data/1d'
file_paht = os.path.join(data_dir,'000001.SZ.feather')
df = pd.read_feather(file_paht)
keep, metrics =strat2_momentum_vol_calmdd(df)
print(keep,metrics)
策略3:趋势突破 + 放量(Donchian 55 新高 + EMA20>EMA60) 逻辑:
- 最高价触及/突破 DonchianChannel(55) 上轨
- 近3日均量 > 近20日均量 * 1.5
- EMA20 > EMA60
python
import numpy as np
import pandas as pd
import os
# ta 库的指标
from ta.volatility import DonchianChannel
from ta.trend import EMAIndicator
def strat3_breakout_volume(df: pd.DataFrame):
high, close, volume = df['high'], df['close'], df['volume']
dc = DonchianChannel(high=high, low=df['low'], close=close, window=55)
dc_high = dc.donchian_channel_hband()
ema20 = EMAIndicator(close, window=20).ema_indicator()
ema60 = EMAIndicator(close, window=60).ema_indicator()
vol3 = volume.rolling(3, min_periods=3).mean()
vol20 = volume.rolling(20, min_periods=20).mean()
if np.isnan(dc_high.iloc[-1]) or np.isnan(ema20.iloc[-1]) or np.isnan(ema60.iloc[-1]):
return False, {}
cond_break = high.iloc[-1] >= dc_high.iloc[-1]
cond_vol = (not np.isnan(vol3.iloc[-1]) and not np.isnan(vol20.iloc[-1]) and vol3.iloc[-1] > vol20.iloc[-1] * 1.5)
cond_trend = ema20.iloc[-1] > ema60.iloc[-1]
keep = bool(cond_break and cond_vol and cond_trend)
metrics = {
"ema20_over_ema60": float(ema20.iloc[-1] / max(ema60.iloc[-1], 1e-6)),
"volume_surge": float(vol3.iloc[-1] / max(vol20.iloc[-1], 1e-6)) if (not np.isnan(vol3.iloc[-1]) and not np.isnan(vol20.iloc[-1])) else np.nan,
"score": float((ema20.iloc[-1] / max(ema60.iloc[-1], 1e-6)) * (vol3.iloc[-1] / max(vol20.iloc[-1], 1e-6)) if keep else 0.0)
}
return keep, metrics
def recent_max_drawdown(close: pd.Series, lookback: int = 60) -> float:
s = close.tail(lookback)
return max_drawdown(s)
def max_drawdown(series: pd.Series) -> float:
# 针对收盘价序列计算最大回撤
if len(series) < 2:
return 0.0
cummax = series.cummax()
dd = series / cummax - 1.0
return float(dd.min())
data_dir =r'data/1d'
file_paht = os.path.join(data_dir,'000001.SZ.feather')
df = pd.read_feather(file_paht)
keep, metrics =strat3_breakout_volume(df)
print(keep,metrics)
策略4:均值回归 + 低波动过滤 + RSI 反转 逻辑:
- 收盘 > SMA(200)(长期不差)
- RSI(14) 昨日 < 30 且今日上穿昨日(超卖反弹)
- BB 带宽(20,2) < 近252日带宽中位数(低波动期博反弹)
python
import numpy as np
import pandas as pd
import os
# ta 库的指标
from ta.volatility import BollingerBands
from ta.trend import SMAIndicator
from ta.momentum import RSIIndicator
def strat4_meanrev_rsi_lowvol(df: pd.DataFrame):
close = df['close']
if len(df) < 252:
return False, {}
ma200 = SMAIndicator(close, window=200).sma_indicator()
rsi = RSIIndicator(close, window=14).rsi()
bb = BollingerBands(close, window=20, window_dev=2)
mid = bb.bollinger_mavg()
up = bb.bollinger_hband()
low = bb.bollinger_lband()
# 带宽 = (上-下)/中
bbw = (up - low) / mid.replace(0, np.nan)
bbw_hist = bbw.tail(252).dropna()
if any(np.isnan([ma200.iloc[-1], rsi.iloc[-1], rsi.iloc[-2]])) or len(bbw_hist) == 0 or np.isnan(bbw.iloc[-1]):
return False, {}
cond_trend = close.iloc[-1] > ma200.iloc[-1]
cond_rsi = (rsi.iloc[-2] < 30) and (rsi.iloc[-1] > rsi.iloc[-2])
cond_lowvol = bbw.iloc[-1] < bbw_hist.median()
keep = bool(cond_trend and cond_rsi and cond_lowvol)
metrics = {
"rsi14": float(rsi.iloc[-1]),
"bbw": float(bbw.iloc[-1]),
"bbw_median_252": float(bbw_hist.median()),
"ma200": float(ma200.iloc[-1]),
"score": float((bbw_hist.median() / max(bbw.iloc[-1], 1e-9)) if keep else 0.0)
}
return keep, metrics
def recent_max_drawdown(close: pd.Series, lookback: int = 60) -> float:
s = close.tail(lookback)
return max_drawdown(s)
def max_drawdown(series: pd.Series) -> float:
# 针对收盘价序列计算最大回撤
if len(series) < 2:
return 0.0
cummax = series.cummax()
dd = series / cummax - 1.0
return float(dd.min())
data_dir =r'data/1d'
file_paht = os.path.join(data_dir,'000001.SZ.feather')
df = pd.read_feather(file_paht)
keep, metrics =strat4_meanrev_rsi_lowvol(df)
print(keep,metrics)
策略5:波动扩张 + 布林上破 + OBV 高分位(量价共振) 逻辑:
- 收盘 > BB 上轨(动量爆发)
- 带宽(20,2) > 近60日带宽中位数 * 1.2(波动扩张确认)
- OBV 位于近120日历史分位 > 0.8(量能趋势强)
python
import numpy as np
import pandas as pd
import os
# ta 库的指标
from ta.volatility import BollingerBands
from ta.volume import OnBalanceVolumeIndicator
def strat5_bbw_expand_obv(df: pd.DataFrame):
close, volume = df['close'], df['volume']
bb = BollingerBands(close, window=20, window_dev=2)
mid = bb.bollinger_mavg()
up = bb.bollinger_hband()
lowb = bb.bollinger_lband()
bbw = (up - lowb) / mid.replace(0, np.nan)
if any(np.isnan([up.iloc[-1], bbw.iloc[-1]])):
return False, {}
# 历史带宽中位
bbw_median_60 = bbw.rolling(60, min_periods=60).median()
if np.isnan(bbw_median_60.iloc[-1]):
return False, {}
obv = OnBalanceVolumeIndicator(close, volume).on_balance_volume()
obv_look = obv.tail(120).dropna()
if len(obv_look) < 30:
return False, {}
# OBV 分位(基于近120日)
obv_rank = obv_look.rank(pct=True).iloc[-1]
cond_bb_break = close.iloc[-1] > up.iloc[-1]
cond_bbw_expand = bbw.iloc[-1] > bbw_median_60.iloc[-1] * 1.2
cond_obv_high = obv_rank > 0.8
keep = bool(cond_bb_break and cond_bbw_expand and cond_obv_high)
metrics = {
"bbw": float(bbw.iloc[-1]),
"bbw_median_60": float(bbw_median_60.iloc[-1]),
"obv_pctile": float(obv_rank),
"score": float((bbw.iloc[-1] / max(bbw_median_60.iloc[-1], 1e-9)) * obv_rank if keep else 0.0)
}
return keep, metrics
data_dir =r'data/1d'
file_paht = os.path.join(data_dir,'000001.SZ.feather')
df = pd.read_feather(file_paht)
keep, metrics =strat5_bbw_expand_obv(df)
print(keep,metrics)
上面这5个策略,返回值keep 为True则表示该股票满足上面的条件,可以在上面的基础上遍历每个股票,就可以筛选出符合条件的所有股票了。
风险提示
本文所示的选股策略仅为教学与研究演示 ,主要目的在于帮助读者理解如何使用 Python 与 ta
库实现多技术指标的量化筛选。
- 非投资建议:文中代码与逻辑并非任何证券买卖的推荐或承诺,不构成投资建议。
- 数据与假设局限:策略基于历史数据与技术指标计算,未考虑交易成本、滑点、停复牌、分红送配、指数暴露等实际交易因素。
- 市场风险 :技术指标可能在不同市场环境下失效;单一因子或短期信号可能带来较大回撤。
- 执行风险:实盘运行中,行情数据质量、交易时延、资金体量都会影响结果。
- 学习目的:请读者将本策略作为学习与研究的工具,切勿直接用于实盘交易。若据此操作,风险自担。
今天关于选股的内容就分享到这里了,希望对大家有所帮助。