短线选股绝招保姆级教程:从零搭建量化实战项目 短线选股绝招保姆级教程:从零搭建量化实战项目 看了一堆教程还是不会写项目?别急,这篇短线选股绝招保姆级教程带你从零搭建。 项目目标与痛点直击 很多开发者朋友在GitHub上收藏了几百个“量化交易”项目,代码看着都懂,真动手跑起来就报错,或者逻辑完全无法落地。核心痛点在于:教程只讲语法,不讲工程化;只给结果,不给推导过程。 为了打破这个僵局,我们基于Python构建一个可复现、可回测的短线选股系统。目标不是让你立刻赚大钱,而是让你掌握数据获取、因子计算、策略回测、风险控制的全链路工程能力。 本项目参考了vnpy官方源码仓库中关于回测引擎的设计思想,但做了极致的简化,确保初学者能在半小时内跑通核心逻辑。 目录结构设计 一个规范的工程项目,结构清晰是第一步。我们采用模块化设计,避免所有代码堆在一个文件里。 stock-picker/ ├── config/ │ └── settings.py # 配置文件:API密钥、回测参数 ├── data/ │ ├── fetcher.py # 数据获取模块 │ └── processor.py # 数据清洗与预处理 ├── strategy/ │ └── short_term.py # 核心策略:短线选股逻辑 ├── backtest/ │ └── engine.py # 简易回测引擎 ├── utils/ │ └── logger.py # 日志工具 ├── main.py # 主入口 └── requirements.txt # 依赖库 这种结构的好处是:数据、策略、回测解耦。当你想更换数据源时,只需修改data/fetcher.py,策略代码无需变动。这就是工程化思维的体现。 核心代码实现:短线因子计算 短线选股的核心在于动量与波动率的平衡。我们选取两个经典因子:5日收益率(Momentum) 和 20日波动率(Volatility)。 1. 数据获取模块 这里我们使用akshare库,它是国内开发者获取A股数据最友好的开源库之一。 # data/fetcher.py import akshare as ak import pandas as pd from datetime import datetime, timedelta class DataFetcher: def __init__(self, symbol: str): 初始化数据获取器 :param symbol: 股票代码,如 '600519' self.symbol = symbol self.df = None def fetch_daily_data(self, days: int = 100) - pd.DataFrame: 获取最近N天的日线数据 :param days: 获取天数 :return: 包含OHLCV的DataFrame end_date = datetime.now().strftime('%Y%m%d') start_date = (datetime.now() - timedelta(days=days)).strftime('%Y%m%d') try: # 获取前复权数据,避免除权除息导致的股价断崖 self.df = ak.stock_zh_a_hist( symbol=self.symbol, period=daily, start_date=start_date, end_date=end_date, adjust=qfq ) # 标准化列名,统一为小写英文 self.df.columns = ['date', 'code', 'open', 'high', 'low', 'close', 'volume', 'amount', 'amplitude', 'pct_chg', 'vol_ratio'] self.df['date'] = pd.to_datetime(self.df['date']) self.df.set_index('date', inplace=True) return self.df except Exception as e: print(f数据获取失败: {e}) return pd.DataFrame() 关键点解析: 前复权(qfq):短线交易必须使用复权数据,否则历史K线在分红送股后会出现虚假的下跌信号,导致策略失效。 异常处理:网络请求不稳定是常态,必须捕获异常并返回空DataFrame,防止程序崩溃。 2. 因子计算与策略逻辑 这是项目的灵魂。我们定义一个简单的打分系统:动量越强得分越高,波动率适中(不过高)得分越高。 # strategy/short_term.py import pandas as pd import numpy as np class ShortTermStrategy: def __init__(self, momentum_window: int = 5, vol_window: int = 20): :param momentum_window: 动量计算窗口(天) :param vol_window: 波动率计算窗口(天) self.momentum_window = momentum_window self.vol_window = vol_window def calculate_factors(self, df: pd.DataFrame) - pd.DataFrame: 计算核心因子并打分 # 1. 计算日收益率 df['daily_return'] = df['close'].pct_change() # 2. 计算动量因子:最近5天的累计收益率 df['momentum'] = df['close'].pct_change(self.momentum_window) # 3. 计算波动率因子:最近20天收益率的标准差 df['volatility'] = df['daily_return'].rolling(window=self.vol_window).std() # 4. 数据清洗:去除NaN值 df.dropna(subset=['momentum', 'volatility'], inplace=True) # 5. 标准化处理(Z-Score),使不同量纲的因子可比 # 动量越大越好,波动率越低越好(短线规避高波动风险) df['momentum_score'] = (df['momentum'] - df['momentum'].mean()) / df['momentum'].std() df['volatility_score'] = (df['volatility'].mean() - df['volatility']) / df['volatility'].std() # 6. 综合得分:动量权重60%,波动率权重40% df['composite_score'] = 0.6 * df['momentum_score'] + 0.4 * df['volatility_score'] return df def select_stock(self, df: pd.DataFrame, top_n: int = 1) - list: 根据综合得分选出前N只股票 # 取最后一条有效数据(即最新交易日) latest_data = df.iloc[-1] # 实际应用中应在全市场范围内排序,此处简化为单股示例 # 若得分大于0,认为具备短线买入价值 if latest_data['composite_score'] 0: return [latest_data['code']] return [] 避坑指南: Z-Score标准化:直接相加不同因子的原始值是没有意义的,因为收益率是百分比,波动率也是百分比但量级不同。标准化后,分数代表“偏离平均水平的程度”,这才是可比的。 Rolling窗口:rolling(window=20).std() 是滚动标准差,它反映了最近20天的波动情况,而不是全周期的波动,这对短线至关重要。 运行与测试:简易回测引擎 有了策略,必须验证其有效性。我们写一个极简的回测引擎,模拟从2023年1月1日至今的交易。 # backtest/engine.py import pandas as pd class SimpleBacktester: def __init__(self, initial_capital: float = 100000): self.initial_capital = initial_capital self.current_capital = initial_capital self.trades = [] def run(self, strategy, data_fetcher): 执行回测 :param strategy: 策略实例 :param data_fetcher: 数据获取实例 df = data_fetcher.fetch_daily_data(days=365) if df.empty: return # 预计算所有历史因子 df = strategy.calculate_factors(df) # 遍历每一个交易日(跳过前20天,因为波动率需要20天数据) for i in range(20, len(df)): current_date = df.index[i] current_price = df.iloc[i]['close'] # 获取截至当前日期的历史数据(防止未来函数!) hist_df = df.iloc[:i+1] # 获取信号 signal = strategy.select_stock(hist_df, top_n=1) if signal: # 简化逻辑:次日开盘价买入 if i + 1 len(df): buy_price = df.iloc[i+1]['open'] # 假设全仓买入 shares = self.current_capital / buy_price # 扣除手续费(万3) fee = shares * buy_price * 0.0003 self.current_capital -= (shares * buy_price + fee) # 假设持有5天后卖出 sell_day = i + 6 if sell_day len(df): sell_price = df.iloc[sell_day]['close'] sell_fee = shares * sell_price * 0.0003 self.current_capital += (shares * sell_price - sell_fee) # 记录交易 self.trades.append({ 'buy_date': df.index[i+1], 'sell_date': df.index[sell_day], 'buy_price': buy_price, 'sell_price': sell_price, 'pnl': (sell_price - buy_price) * shares - fee - sell_fee }) # 输出结果 self.report() def report(self): 生成回测报告 if not self.trades: print(无交易记录) return total_pnl = sum(t['pnl'] for t in self.trades) win_rate = len([t for t in self.trades if t['pnl'] 0]) / len(self.trades) * 100 max_drawdown = self.calculate_max_drawdown() print(f初始资金: {self.initial_capital:,.2f}) print(f期末资金: {self.current_capital:,.2f}) print(f总收益: {total_pnl:,.2f}) print(f胜率: {win_rate:.2f}%) print(f最大回撤: {max_drawdown:.2f}%) def calculate_max_drawdown(self): # 此处简化,实际应基于净值曲线计算 return 10.0 核心概念:未来函数 在回测中,hist_df = df.iloc[:i+1] 这一行至关重要。它确保在第i天做决策时,只能看到第i天及之前的数据。如果你用了第i+1天的收盘价来计算第i天的因子,这就是“未来函数”,会导致回测结果虚高,实盘必亏。 优化扩展与避坑指南 跑通基础版后,你会遇到几个实际问题,这也是从“教程”到“工程”的跨越点。 1. 数据缺失处理 A股每天都有停牌股。如果在停牌日,close价格为NaN,或者成交量为0。 解决方案:在processor.py中增加过滤逻辑。 def clean_data(df): # 过滤成交量为0的停牌日 df = df[df['volume'] 0] # 前向填充少量缺失值 df['close'].fillna(method='ffill', inplace=True) return df 2. 参数过拟合 你可能发现,把momentum_window从5改成3,收益率翻倍。这很可能是过拟合。 解决方案:进行参数敏感性分析。不要只测试一个参数,而是测试一个范围(如3-10天),观察策略绩效是否稳定。如果只在某个特定参数下表现好,说明策略不可靠。 3. 实盘对接 回测是历史,实盘是现实。 延迟问题:行情数据通常有毫秒级延迟,你的策略信号发出后,价格可能已经变了。 滑点:回测中假设按收盘价成交,实盘中可能因流动性不足无法成交,或成交价偏离。建议在回测中加入slippage参数,例如buy_price = open * 1.001。 4. 日志与监控 在生产环境中,必须记录每一步决策。 # utils/logger.py import logging import os def setup_logger(name=stock_picker): if not os.path.exists(logs): os.makedirs(logs) logging.basicConfig( filename=flogs/{name}.log, level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) return logging.getLogger(name) 在策略每次触发买卖时,调用logger.info(fBuy signal at {price})。这是排查实盘bug的唯一线索。 小结 这篇短线选股绝招保姆级教程,带你完成了从数据获取到因子构建,再到回测验证的完整闭环。 回顾一下我们学到的核心工程能力: 模块化设计:数据、策略、回测分离,便于维护。 因子标准化:Z-Score让不同维度的指标可比。 防止未来函数:回测中严格限制数据可见范围。 异常处理与日志:让程序在脏数据和网络波动下依然健壮。 这个项目的代码框架已经搭建完毕,你可以基于此替换因子(如加入RSI、MACD),或更换数据源(如从akshare切换到Tushare)。真正的量化交易,始于代码,终于风控。 这个知识点你面试被问过吗?比如“如何防止回测中的未来函数”或“因子标准化为什么要用Z-Score而不是Min-Max”,留言说说你的看法或遇到的坑,我们一起交流。