Python量化选股实操教程,内附5个实战案例源码

前几天有个粉丝问我

"能 能 能 必须能"

今天这篇文章就教大家如何实现这一功能。

1、行情数据准备

通达信软件在实现条件选股的时候需要下载历史行情数据,同样的Python想要实现这一功能也需要把行情下载下来。

行情数据下载推荐大家使用xtquant+miniQMT,有人说还可以通过Tushare、AKShare、Pytdx库来下载行情,个人是不推荐的。 Tushare免费的接口额度少的可怜,要想下载全部历史行情还得花钱买积分,一年就是大几千。 AKShare是真的好,数据丰富,免费,但是频繁访问会导致IP被封,接口稳定性差。 Pytdx复权数据不好处理,还得自己根据分红配股的数据来算,增加了量化负担。

接下来回归正题,要下行情数据,首先要通过xtquantxtdata.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 库实现多技术指标的量化筛选。

  • 非投资建议:文中代码与逻辑并非任何证券买卖的推荐或承诺,不构成投资建议。
  • 数据与假设局限:策略基于历史数据与技术指标计算,未考虑交易成本、滑点、停复牌、分红送配、指数暴露等实际交易因素。
  • 市场风险 :技术指标可能在不同市场环境下失效;单一因子或短期信号可能带来较大回撤
  • 执行风险:实盘运行中,行情数据质量、交易时延、资金体量都会影响结果。
  • 学习目的:请读者将本策略作为学习与研究的工具,切勿直接用于实盘交易。若据此操作,风险自担。

今天关于选股的内容就分享到这里了,希望对大家有所帮助。

相关推荐
拾忆,想起30 分钟前
Redis发布订阅:实时消息系统的极简解决方案
java·开发语言·数据库·redis·后端·缓存·性能优化
qq_124987075334 分钟前
基于改进蜂群优化算法的高频金融波动率预测系统 (源码+论文+部署+安装)
python·算法·金融·毕业设计·蜂群优化算法
AllyLi022440 分钟前
CondaError: Run ‘conda init‘ before ‘conda activate‘
linux·开发语言·笔记·python
测试老哥42 分钟前
如何用Postman做接口测试?
自动化测试·软件测试·python·测试工具·测试用例·接口测试·postman
SimonKing1 小时前
想搭建知识库?Dify、MaxKB、Pandawiki 到底哪家强?
java·后端·程序员
程序员清风1 小时前
为什么Tomcat可以把线程数设置为200,而不是2N?
java·后端·面试
zhangbaolin1 小时前
open webui源码分析11-四个特征之记忆
python·大模型·memory·open webui
皮皮学姐分享-ppx1 小时前
机器人行业工商注册企业基本信息数据(1958-2023年)
大数据·人工智能·python·物联网·机器人·区块链
MrSYJ1 小时前
nimbus-jose-jwt你都会吗?
java·后端·微服务
Bug生产工厂1 小时前
AI 驱动支付路由(下篇):代码实践与系统优化
后端