
短线选股绝招保姆级教程:从零搭建量化实战项目
看了一堆教程还是不会写项目?别急,这篇短线选股绝招保姆级教程带你从零搭建。
项目目标与痛点直击
很多开发者朋友在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”,留言说说你的看法或遇到的坑,我们一起交流。