用XGBoost撸一个预测股票涨跌的小工具,顺便把Streamlit和akshare也整明白了
搞个预测股价的玩意儿,你说香不香?
有一天我发呆,盯着手里的几只票,突然脑子里冒出一个念头:“要是我能提前知道明天涨不涨就好了。”——哦对,这话听着这么玄乎呢? 但等等,我们不是搞玄学,我们搞机器学习,搞XGBoost,搞点建模实战,搞一搞看它能不能“多赚一毛是一毛”。
你可能在想:
“XGBoost不是那种比赛里用来夺冠的东西吗?我拿来炒股是不是有点离谱?”
离谱是真离谱,但是!——不试你怎么知道呢。
先搞明白几个概念,不然一脸懵
我们要做的事是这样的:
- 输入过去几年的股价
- 算出一些指标(什么MA、RSI、MACD这些)
- 判断:明天这玩意儿会不会涨
不是预测涨多少,只是一个涨 or 不涨,就是一个0 or 1的二分类问题。
听懂了吧?这玩意儿可以喂给XGBoost吃。
Step 1:数据从哪儿来?akshare走一个
你想撸股票的数据,akshare 是宝藏。话说回来,akshare文档是有点随缘(我本人对它的接口命名体感也不太好),但谁让它能免费抓A股呢。
import akshare as ak
stock_code = "000001" # 平安银行
df = ak.stock_zh_a_hist(
symbol=stock_code,
period="daily",
start_date="20220101",
end_date="20250514",
adjust="qfq" # 可选:'qfq'(前复权)、'hfq'(后复权)、''(不复权)
)
print(df)
注意事项:
- 不要忘了复权,不然高低点一对比,全是错觉。
- 日期是字符串,不是datetime,这种小坑很多人踩。
Step 2:特征工程——能提炼点有用信息吗?
光拿收盘价没啥意思,我们得构造一些“技术指标”,给模型点启发。
- MA(移动平均)
- RSI(相对强弱指数)
- MACD(指数平滑移动平均差值)
也可以用其它指标,这里暂时用这3个做演示
咱直接撸代码,不讲大道理:
import talib
import numpy as np
df['MA5'] = talib.SMA(df['收盘'], timeperiod=5)
df['RSI'] = talib.RSI(df['收盘'], timeperiod=14)
df['MACD'], _, _ = talib.MACD(df['收盘'], fastperiod=12, slowperiod=26, signalperiod=9)
还有一个核心点:我们要构造“目标变量”——也就是“明天是否上涨”:
df['target'] = (df['收盘'].shift(-1) > df['收盘']).astype(int)
注意!
shift(-1)
的意思是“看明天”astype(int)
是让它变成 0 和 1,XGBoost爱吃这个
再来一招:
df.dropna(inplace=True) # 特征工程之后一定要丢掉NA,不然模型直接闹罢工
Step 3:XGBoost走起,别怕,这模型不难
安装一下:
pip install xgboost scikit-learn
代码整上:
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
features = ['MA5', 'RSI', 'MACD']
X = df[features]
y = df['target']
X_train, X_test, y_train, y_test = train_test_split(X, y, shuffle=False, test_size=0.2)
model = XGBClassifier(n_estimators=100, max_depth=3, learning_rate=0.1)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"准确率:{acc:.2f}")
问题来了——不shuffle数据可以吗?
可以!在时序数据里,不能乱洗牌! 否则模型就提前知道答案,那是作弊,不是预测。
Step 4:搞个界面给自己和朋友玩玩
别拿命令行唬人,咱用 Streamlit,一点不费劲。
pip install streamlit plotly
Streamlit版本完整代码
import streamlit as st
import akshare as ak
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from xgboost import XGBClassifier
import joblib
import talib
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
# 用于技术指标计算
def add_features(df):
df['MA5'] = talib.SMA(df['close'], timeperiod=5)
df['RSI'] = talib.RSI(df['close'], timeperiod=14)
df['MACD'], _, _ = talib.MACD(df['close'], fastperiod=12, slowperiod=26, signalperiod=9)
return df
def prepare_data(df):
df['target'] = (df['close'].shift(-1) > df['close']).astype(int)
df = add_features(df)
df.dropna(inplace=True)
return df
# 页面标题
st.set_page_config(page_title="股票涨跌预测", layout="wide")
st.title("股票涨跌预测系统")
# 用户输入
stock_code = st.text_input("请输入股票代码(如000001)", "000001")
start_date = st.date_input("起始日期", pd.to_datetime("2023-01-01"))
end_date = st.date_input("结束日期", pd.to_datetime("2025-05-31"))
if st.button("开始预测"):
try:
st.info("正在获取数据并处理...")
df_raw = ak.stock_zh_a_hist(symbol=stock_code, period="daily", start_date=start_date.strftime('%Y%m%d'),
end_date=end_date.strftime('%Y%m%d'), adjust="qfq") # 可选:'qfq'(前复权)、'hfq'(后复权)、''(不复权)
df = df_raw.rename(columns={
"开盘": "open",
"收盘": "close",
"最高": "high",
"最低": "low",
"成交量": "volume",
"日期": "date"
})
df['date'] = pd.to_datetime(df['date'])
df.set_index("date", inplace=True)
df = prepare_data(df)
features = ['MA5', 'RSI', 'MACD']
X = df[features]
y = df['target']
X_train, X_test, y_train, y_test = train_test_split(X, y, shuffle=False, test_size=0.2)
model = XGBClassifier(n_estimators=100, max_depth=3, learning_rate=0.1)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"预测准确率:{acc:.2f}")
st.success(f"预测准确率:{acc:.2f}")
print(df)
print(len(y_pred))
# df['pred'] = y_pred
# df['correct'] = df['pred'] == df['target']
# 只给测试集对应的df行赋值
df.loc[X_test.index, 'pred'] = y_pred
df.loc[X_test.index, 'correct'] = df.loc[X_test.index, 'pred'] == df.loc[X_test.index, 'target']
print(df)
# 绘制K线图
fig = go.Figure(data=[
go.Candlestick(
x=df.index,
open=df['open'],
high=df['high'],
low=df['low'],
close=df['close'],
name="K线"
)
])
df.dropna(inplace=True)
# 正确上涨(预测1,实际1)
correct_df = df[df['correct'] & (df['pred'] == 1)]
fig.add_trace(go.Scatter(
x=correct_df.index,
y=correct_df['close'],
mode='markers',
marker=dict(symbol='triangle-up', color='green', size=10),
name='预测正确上涨'
))
# 预测上涨但错了
wrong_df = df[~df['correct'] & (df['pred'] == 1)]
fig.add_trace(go.Scatter(
x=wrong_df.index,
y=wrong_df['close'],
mode='markers',
marker=dict(symbol='triangle-down', color='red', size=10),
name='预测错误上涨'
))
st.plotly_chart(fig, use_container_width=True)
st.caption("本应用仅用于技术演示,不构成投资建议。股市有风险,入市需谨慎。")
except Exception as e:
st.error(f"出错啦:{e}")
预测正确的点你可以用绿色三角,错误的红色倒三角标出来。记得加点 legend,别整一堆符号让人看不懂。
模型导出导入,别让你CPU白跑
如果模型训练出来的准确率很高,那模型必须保存下来,方便下次直接使用。
保存模型(推荐原生格式)
model.save_model("xgb_stock_model.json")
也可以用 joblib:
import joblib
joblib.dump(model, "xgb_stock_model.pkl")
加载模型再预测
loaded_model = XGBClassifier()
loaded_model.load_model("xgb_stock_model.json")
pred = loaded_model.predict(X_test)
或者 joblib 方式:
loaded_model = joblib.load("xgb_stock_model.pkl")
pred = loaded_model.predict(X_test)
👀 注意!
- 预测时的特征顺序必须和训练时一致!不然模型疯了你也疯。
- 用了 scaler?它也要存!
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
joblib.dump(scaler, "scaler.pkl")
再加载回来:
scaler = joblib.load("scaler.pkl")
X_test = scaler.transform(X_test)
风险提示,不然我怕你报警
这套玩意儿,说白了就是个技术演示,不是让你all in。XGBoost再牛,也只是数学模型,预测不可能100%准。
聪明的你一定会发现——其实搞个预测涨跌的项目,门槛并不高,关键是你肯不肯动手。
如果你觉得文章不错,记得帮花姐点点再看