news 2026/10/19 11:08:05

用最小二乘法拟合股票K线趋势:Python数据分析与可视化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用最小二乘法拟合股票K线趋势:Python数据分析与可视化

1. 为什么给K线做一条直线拟合:趋势可视化的起点

1.1 从折线图到拟合线:先说说我遇到的实际问题

很多新手拿到股票数据之后,第一件事就是画收盘价折线图。看着起伏的价格曲线,心里想着“这票最近到底是在涨还是在跌”。可说实话,单看折线图,趋势这个事非常容易误判——尤其是当波动比较大的时候,眼睛很容易被最近几根大阳线或者大阴线带跑,反而看不清整段走势的整体方向。

我之前在整理某只样本标的的日线数据时就遇到这种事。当时股价在一个区间里来回震荡,折线图看起来像一团乱麻。我借着绘图工具放大缩小时,隐约觉得它整体重心有点往上抬,但说不准。后来用最小二乘法对这组股票数据做了一次线性拟合,画出一条趋势直线,整个判断立刻清楚了很多:斜率是正的,重心确实在缓慢上移。趋势线一画出来,图表信息量完全不同了。

这篇文章就是把这套完整流程拆开来讲:从数据准备、清洗口径,到用np.polyfit做线性拟合,再到用matplotlib绘图呈现出带拟合线的趋势图,以及拟合结果怎么解读、有哪些容易翻车的地方。适合正在学数据分析、想把量化分析基础操作落地的同学,也适合想给自己的看盘图表加一条“参考趋势线”的投资者。

1.2 线性拟合在股票分析里到底扮演什么角色

先明确一件事:对股票数据做线性拟合,不是为了预测明天涨跌,而是为了回答一个更基础的问题——过去这一段区间,价格的“平均走向”到底是什么。

这里的核心思想是最小二乘法。它的目标很朴素:找一条直线,使得所有样本点到这条直线的竖直距离(残差)的平方和最小。为什么用平方而不是直接用距离?因为距离有正有负,直接相加会互相抵消,平方之后不仅统一了方向,还放大了偏离较大的点的影响,让拟合结果更倾向于照顾大多数点。

这就是为什么拟合出来的直线并不一定穿过任何一个真实收盘价,但它在统计意义上代表了这组数据的主体趋势。在股票分析场景里,这条直线可以回答几个实际问题:这段区间整体是上升还是下跌?趋势的斜率大致是多少?价格的波动中心在哪里?这些信息在裸看K线图时很难量化,但一条拟合直线可以直观地给出来。

不过也要说清楚:线性拟合是对历史数据的描述性工具,不是预言工具。它假设价格围绕一条直线波动,但股票市场明显不是这么简单的过程。后面我会单独讲它的局限,这里先不神化它。

2. 数据准备:先让数据“能算”再“能画”

2.1 本地CSV读取与字段检查

不管数据从哪里来,到最后一定得整理成结构化表格。我习惯先用pandas读进来再检查字段,因为后续做拟合和绘图的代码都建立在干净的DataFrame上。

假设你手头有一份日线数据,包含日期、开盘价、最高价、最低价、收盘价、成交量这几个常见字段。CSV文件长这样:

字段名类型说明
dateobject/str交易日期,需要解析成datetime
openfloat开盘价
highfloat最高价
lowfloat最低价
closefloat收盘价
volumeint/float成交量

读取代码很常规:

import pandas as pd df = pd.read_csv("stock_daily.csv") df["date"] = pd.to_datetime(df["date"]) # 把日期字符串转成datetime类型 df = df.sort_values("date").reset_index(drop=True) # 按时间升序排列并重置索引 print(df.head())

这里有一个很小的细节,但却很容易踩坑:pd.to_datetime解析日期是后续所有日期相关绘图工作的前提。如果你跳过这一步,matplotlib会把日期当成一串文本处理,画出来的横轴坐标完全是乱的。我早些年第一次画时间序列图时就是没做转换,结果横轴间距完全不对,还以为是图表库的问题。

另外,检查一下有没有重复日期。同一个交易日出现两行数据,多半是数据源合并时出了问题。重复日期会直接污染拟合结果,因为它等于给某些点加了额外权重。处理方式很简单:

df = df.drop_duplicates(subset="date", keep="last").reset_index(drop=True)

2.2 复权价格与缺失值:新手最常栽的两个坑

接下来是很多人忽略的环节:复权。股票在分红、送股、配股时会发生除权除息,价格会直接跳空。比如某只股票前一天收盘20元,第二天因为10派5,开盘价直接变成18元左右。这种“断崖”不是市场行为,而是会计口径造成的价格修正。如果在未复权的数据上直接做线性拟合,拟合直线会被这种人为跳空严重带偏,图形上看着像崩盘,实际上什么都没发生。

行情软件里通常有“前复权”和“后复权”两种选项。前复权是以当前价格为基准,把历史价格向下调整;后复权是以历史价格为基准,把后续价格向上调整。我的经验是:做历史趋势分析时优先用前复权数据,因为它的最新价格和现实行情一致,画图出来最贴近大家日常看到的走势。如果你拿到的数据接口没有复权选项,至少要在拟合前检查一下close序列有没有因为除权而出现莫名其妙的巨大跳变,有的话就得换复权口径。

缺失值也比较常见。停牌日、节假日、数据断更都会留下NaN。拟合前必须处理,否则np.polyfit会直接报错或者算出NaN结果。最简单的办法是:

df = df.dropna(subset=["close"])

如果缺失的是中间某几天,而你希望保持时间的连续性,也可以用前向填充补上,但用在拟合上最好还是先删掉,因为填充值本身并没有交易含义,会扭曲趋势。

2.3 没有真实数据时怎么先验证流程

我不太建议刚开始跑流程就直接上真实行情——数据量一大,问题往往出在数据质量上,而不是算法本身,很容易打击信心。更稳的做法是先造一段模拟数据,把整体流程跑通,确认代码没问题,再替换成真实数据。

模拟数据可以很简单:

import numpy as np import pandas as pd np.random.seed(42) n = 120 dates = pd.date_range("2024-01-01", periods=n, freq="B") # B代表工作日,跳过周末 trend = np.linspace(10, 13, n) # 整体向上走的基准趋势 noise = np.random.normal(0, 0.08, n) # 随机噪声,模拟价格波动 close = trend + noise df = pd.DataFrame({"date": dates, "close": close})

这段代码生成了一段120个交易日的模拟行情,里面有明确的上行趋势,又叠加了随机噪声,非常适合用来验证后续的拟合和绘图逻辑。等全部流程都跑通了,再把df替换成从CSV读进来的真实数据就行。

3. 拟合核心:np.polyfit背后的算法与参数选择

3.1 最小二乘法的直觉与代码实现

线性拟合本质上是在解一个最优化问题。我们想找到斜率和截距,使得所有点的残差平方和最小:

残差平方和 = Σ(实际收盘价 - 拟合直线值)²

这个公式看着抽象,但想一下物理中的质心概念就懂了:拟合直线是数据点在整体上的“重心走向”。它不追求完全穿过每个点,而是追求对所有点的综合误差最小。

numpy里最直接的接口就是np.polyfit:

x = np.arange(len(df)) # 用0,1,2,...作为自变量 y = df["close"].values # 收盘价作为因变量 coef = np.polyfit(x, y, deg=1) # deg=1表示拟合一次多项式,即直线 slope, intercept = coef print(f"斜率: {slope:.4f}, 截距: {intercept:.4f}")

这里需要解释一个关键设计:为什么自变量用0,1,2,...而不是用日期?因为np.polyfit处理的是数值回归,日期对象不能直接参与运算。把日期映射成等间隔的整数序列,数学意义是“每个交易日间隔相等”,这也符合我们对日线数据的常规假设。

得到系数后,用np.polyval生成拟合直线上的点,方便后续绘图:

fit_line = np.polyval(coef, x)

看代码就知道,整个过程短得让人有点意外。但真正值钱的不是这三行调用,而是你知不知道polyfit背后在算什么、算出来的系数代表什么含义、什么时候这个结果会失真。这些恰恰是文档里不写的东西。

3.2 为什么阶数默认是1而不是更高

np.polyfit之所以把deg参数留给你,是因为你可以选择任意多项式阶数。deg=2是抛物线,deg=3是三次曲线,以此类推。那为什么股票趋势拟合中几乎都用deg=1?

关键在于“过拟合”。阶数越高,拟合曲线越能逼近每一个历史点,但它拟合的不再是趋势,而是噪声。想象你用手掌压平一张被揉皱的纸,一次多项式像是用一个平整的木板去压,压出来的是一个整体平面;高次多项式则像是用无数根手指精确地按压每个褶皱,表面看起来贴合度极高,但换个角度看,那些褶皱根本不是你需要保留的信息。

股票价格噪声非常大,日线级别的随机波动往往远超真实趋势信号。如果用高次多项式拟合,曲线会疯狂摆动去追逐每一根K线的波动,画出来虽然“完美穿越”所有价格点,但完全丧失了趋势分析的价值。线性拟合的意义恰恰在于它“钝感”,它只提取整体走向,忽略短期噪音。

3.3 用R²判断拟合效果:这个值怎么看

光有斜率和截距还不够,你得知道这条拟合线到底解释了多少价格变动。这就引入了R²(拟合优度):

residuals = y - fit_line ss_res = np.sum(residuals ** 2) ss_tot = np.sum((y - np.mean(y)) ** 2) r_squared = 1 - ss_res / ss_tot print(f"R²: {r_squared:.4f}")

R²的取值范围在0到1之间。越接近1表示价格波动中能被这条直线解释的比例越高;接近0则表示价格基本是随机游走,直线拟合没有太大意义。

这里要给新手打个预防针:股票日线数据的R²往往很低,0.2到0.5都算正常。别看到R²低就觉得代码写错了,这是股票市场本身的特性——短周期内影响价格的因素太多,线性趋势只是众多分量中的一小部分。如果你改成周线或者月线数据,聚合之后噪声被平均掉了,R²通常会明显上升。这也是为什么做长周期趋势判断时,我会先对数据做降采样再拟合。

4. 绘图实战:matplotlib产出干净的趋势图

4.1 最小可用版本:收盘价折线 + 拟合直线

接下来进入重头戏:用matplotlib绘图。先把最小可用的版本写出来,后面再逐步加细节。

import matplotlib.pyplot as plt import matplotlib.dates as mdates fig, ax = plt.subplots(figsize=(12, 6)) ax.plot(df["date"], df["close"], lw=1.5, label="收盘价") ax.plot(df["date"], fit_line, "--", color="red", lw=2, label="线性拟合趋势") ax.set_xlabel("日期") ax.set_ylabel("价格") ax.set_title("股票收盘价与线性拟合趋势线") ax.legend() ax.grid(alpha=0.3) plt.tight_layout() plt.show()

这段代码画出来的图已经具备基本的信息表达:真实价格曲线作为背景,拟合直线作为趋势参考。注意fit_line在代码里已经通过np.polyval计算成了和df["close"]长度相同的数组,所以可以直接用df["date"]作为横轴来绘制。

红虚线代表过去这段区间的“平均走向”,比折线图本身更清晰地传递趋势信息。这就是整套任务的核心产出。

4.2 日期坐标轴的坑:为什么刻度总是挤成一团

用matplotlib画时间序列,十个人里有八个会遇到刻度标签重叠的问题。这是因为它默认自动选择刻度位置,日期一多,标签就会互相覆盖,最终画出来的图没法看。

解决办法是手动控制日期格式和刻度间隔:

ax.xaxis.set_major_locator(mdates.MonthLocator(interval=2)) ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d")) fig.autofmt_xdate() # 自动旋转日期标签,避免重叠 plt.xticks(rotation=45)

这里有两个关键点。第一,ax.plot(df["date"], ...)传入的必须是datetime类型,matplotlib内部会把它转换成数值坐标,这样才能用mdates的定位器和格式化器来控制刻度。如果你传入的是字符串列表,后面的MonthLocator根本不起作用。这也是我在数据准备阶段强调日期解析的原因。

第二,fig.autofmt_xdate()会自动调整横轴标签的旋转角度。我个人的习惯是搭配plt.xticks(rotation=45)用,两个叠加之后,10个以上的日期刻度也能排得整整齐齐。

4.3 进阶信息增强:置信带、斜率标注、图表导出

一个真正有价值的趋势图,不应该只画一条线和一条趋势线。我会额外加三样东西:

一是误差带,用来展示价格围绕拟合线的波动范围。用fill_between画出来,能直观看出波动有多大:

pred_std = np.std(y - fit_line) ax.fill_between( df["date"], fit_line - 1.96 * pred_std, fit_line + 1.96 * pred_std, alpha=0.15, color="red", label="约95%波动范围" )

这里的1.96 * pred_std是从正态分布假设推导的近似95%的波动区间。严格来说,样本量不大时应该用t分布的值或残差经验分位数,但画图展示用这个近似完全够用。它给人的直觉是:价格虽然波动,但整体围绕红虚线在一定带宽内运动。

二是关键数值标注。直接在图上写出拟合斜率、R²两个指标,看图时不用再去代码里翻计算结果:

ax.text( 0.02, 0.95, f"斜率: {slope:.4f} R²: {r_squared:.4f}", transform=ax.transAxes, fontsize=12, verticalalignment="top", bbox=dict(boxstyle="round", facecolor="white", alpha=0.7) )

transform=ax.transAxes表示坐标位置以整个坐标轴为参照,0到1之间是相对位置。这样不管数据范围怎么变,标注永远在图的左上角。

三是导出图片。日常分析我会输出300dpi的PNG,保证放大后依然清晰:

fig.savefig("stock_trend.png", dpi=300) plt.close(fig)

plt.close(fig)这一步很多新手会忽略。在交互式环境里不关图会越积越多,严重时内存被拖垮。养成画图后关闭的肌肉记忆,能省掉很多莫名其妙的卡顿问题。

5. 拟合结果解读与常见翻车点

5.1 斜率不是一切:先归一化才能横向比较

很多人画完趋势图,看到斜率是正的就觉得可以买,看到斜率是负的就觉得要跑。这个直觉没毛病,但要注意一个致命的细节:斜率的绝对值没有任何跨股票比较的意义。

原因很简单:斜率的数值大小受价格量纲影响。一只股价200元的股票,就算涨幅很温和,拟合斜率可能也是0.5;另一只股价5元的股票,就算涨得很猛,拟合斜率可能也只有0.03。你拿这两个斜率直接对比,完全没有意义。

正确的做法是把斜率归一化。最常用的方式是用斜率除以区间内的平均价格,得到“单位价格的单位时间变化率”:

normalized_slope = slope / np.mean(df["close"])

这个值相当于把不同价位的股票拉平到同一个起跑线上比较。比如归一化斜率为0.001,意思是每个交易日平均上涨0.1%。如果你想知道年化水平,再乘以每年的交易日数量(通常按250天算)就能粗略估算。

5.2 样本区间的选择会偷偷改变结论

这是隐藏最深的坑。同一只股票,同一套拟合代码,只要你把样本区间换一换,结果可能完全翻转。

比如某只标的前面涨了半年,后面跌了三个月,如果取最近三个月拟合,斜率是负的;取最近一年拟合,斜率可能又是正的。两个结论都对,因为描述的是不同时间尺度下的“平均状态”。没有哪个区间是“唯一正确”的,关键是你得清楚自己分析的是哪一段趋势,以及为什么选这一段。

我踩过的例子是:有次分析标的的时候,我用60个交易日拟合,斜率明显向下;但后来把窗口拉长到120天,发现其实只是最近一个月回调,整体还是上涨中的回调。趋势判断完全取决于你选择了哪个“时间滤波器”。

因此,在做结论前,最好多跑几个窗口长度对比一下:

  • 20日拟合:反映极短期走向,噪声大,波动敏感
  • 60日拟合:约一个季度,适合捕捉中期波段
  • 120日拟合:约半年,适合看中长期趋势

没有一个窗口是永远最优的,但多窗口对比能让你看清当前走势处在哪个层级,避免被单一窗口欺骗。

5.3 别把趋势线当成“未来的路”

最后一个必须泼冷水的点:线性拟合出来的趋势线,本质上是历史数据的均值回归线,它不构成任何买卖依据,更不能被当成预测工具。

股票价格的形成过程包含大量不可预测因素。拟合直线只代表“如果过去的平均状态延续下去,价格大概会沿着这条线走”,但市场的运行显然不是一个稳定线性过程。突发事件、资金流向、行业周期,任何一个因素都可能让价格瞬间偏离拟合线很大距离。

更实际的做法是:把线性拟合当成一个“快速体检工具”,用来初筛趋势状态,发现问题后用其他分析方法交叉验证,而不是单独拿一条直线的斜率做决策。这套流程的价值在于可视化、直观化、历史统计量化,它降低了趋势判断的模糊性,但绝不是在给你一台印钞机。

6. 从拟合到实用:还能往哪个方向扩展

6.1 滚动窗口动态观察斜率变化

静态拟合只能看到“截至今天”的趋势,但市场是动态变化的。一个自然的扩展是引入滚动窗口,计算随时间推移的滚动斜率:

window = 60 df["rolling_slope"] = ( df["close"] .rolling(window) .apply(lambda y: np.polyfit(range(window), y, 1)[0], raw=True) )

这段代码做了这么一件事:每往前滑动一个交易日,就用最近60天的数据重新拟合一条直线,只取斜率存下来。这样你就得到了一个“斜率历史序列”——斜率在0轴上方持续走高,说明趋势在加速上行;斜率跌破0轴,说明价格从上涨转入下跌;斜率在0轴附近反复穿越,说明处于震荡期。

画图时把滚动斜率和主图做成一个双轴图,效果非常好。滚动斜率做出来的曲线比传统技术指标早半拍,因为它本质上是价格的导数,数学上天然领先于价格本身。

6.2 对数价格拟合:长周期分析的更优选择

前面我们一直对原始收盘价做线性拟合。但如果把时间拉长到几年,这里有个数学上的隐患:价格增长通常不是线性的,而是近似复利式的指数增长。指数曲线在普通坐标里会越来越陡,一条直线很难描述这种动态加速度。

解决方案很简单:对收盘价取对数,再拟合直线。

log_close = np.log(df["close"]) log_coef = np.polyfit(x, log_close, 1) log_slope, log_intercept = log_coef

对数空间里的一次函数,对应原空间里的指数趋势。此时拟合出来的斜率有一个很漂亮的含义:它直接代表“日对数收益率”的近似水平。如果年化对数斜率为0.15,相当于趋势年化收益率约16%。这对长周期趋势分析特别有用,而且数值不再受价格绝对量纲影响,天然具备横向可比性。

之前我一直用原始价格拟合画图,后来发现拉长到500天以上时趋势线经常偏高或偏低,换成对数拟合之后明显合理很多。同样的道理也适用于计算长期均线——用对数通道做出来的均线系统,在长周期上更贴近真实走势。

6.3 与移动均线组合使用

线性拟合趋势线和移动均线是一对很好的互补工具。均线反映的是“最近N日平均成本”,拟合直线反映的是“最近N日统计趋势方向”。二者结合,可以互相验证信号。

比如我常用的做法是:当拟合斜率由负转正,同时收盘价上穿60日均线,视为趋势弱转强的候选信号;当拟合斜率由正转负,同时收盘价跌破60日均线,视为趋势转弱的提醒。注意我说的是“候选”和“提醒”,不是买卖信号——技术分析最大的陷阱就是追求确定性,而市场从来不给确定性。

把这些工具组合起来,画一张信息完整的趋势图,比单看任何一条线都要可靠得多。你要的不只是一种分析手段,而是一套能互相印证的观察体系。

最后分享一个我自己很受用的检查习惯:每次画完趋势图,先别急着读结论,把时间窗口来回调三次,看看结论是否稳定。如果三个合理窗口得出的斜率符号一致,我会认为趋势判断比较可靠;如果符号不一致,说明当前价格正处在一个多空分歧区域,这时候线性拟合给出的信号本身也是紊乱的,最好的选择是等待区间收敛。这套流程做完,np.polyfit和matplotlib在你手里就不再是两条孤立的知识点,而是一整套“数据到可视化再到结论”的完整链路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 11:05:41

基于YOLOv8的安全帽佩戴检测:从数据构建到边缘部署全流程实战

1. 工地安全监管的真实痛点与视觉检测的切入点干了这么多年计算机视觉落地项目,我越来越觉得,真正有价值的不是模型在公开数据集上刷到多高的mAP,而是能不能在工地上那台满是灰尘的工控机上,顶着大太阳和逆光,把没戴安…

作者头像 李华
网站建设 2026/10/19 11:05:36

Agent Harness:让Agent从“能对话”到“能干活”的关键支撑层

先说结论:如果你让我用一句话定义,Agent Harness 就是让 Agent 从“能对话”变成“能干活”的那一层支撑系统。模型负责生成想法,框架负责编排步骤,但真正让 Agent 能稳定跑起来、能调用工具、能在出错后恢复、能被观测和评估的&a…

作者头像 李华
网站建设 2026/10/19 11:04:55

diagram-design:从草图到系统,如何画出一张能评审的架构图

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题第一次看到 diagram-design 这个标题,很多人会以为它只是某个画图工具的皮肤或者模板集合。但真正做过系统设计、技术方案评审、跨团队沟通的人会立刻意识到,这个词背后藏着一个更…

作者头像 李华
网站建设 2026/10/19 11:04:36

自动化数据增强实战:从策略搜索到模型泛化提升

数据增强在大数据挖掘项目里,一直是被低估的一环。很多人觉得数据量都这么大了,增强还有啥必要?但实际做项目时你会发现,真正影响模型上线效果的,往往不是你收集了多少原始数据,而是你能不能把这些数据“榨…

作者头像 李华
网站建设 2026/10/19 11:04:36

编程Agent自研万亿参数MoE+强化学习,性能提升61%

前阵子我看到一条消息:某款主流AI编程编辑器发布了自研的第二代编程Agent模型,1万亿参数MoE架构加上强化学习训练路线,端到端任务性能相比上一代直接提升了61%。这个数字让我立刻想把背后的技术链路重新拆一遍。倒不是因为它多轰动&#xff0…

作者头像 李华
网站建设 2026/10/19 11:03:45

FastAPI+Uvicorn离线部署全攻略:无网络服务器依赖打包与安装实战

有时候最折腾人的不是写代码,而是把代码弄到一台“干干净净”的服务器上。上周我接手一个内部系统的后台,用的是FastAPI做接口层,Uvicorn当ASGI服务器跑生产服务。开发机上一切正常,requirements一装就完事。等我把项目传到内网服…

作者头像 李华