strategy · 四大库合体 2026-09-28

流水线:
拉数据(datetime 定日期)→ pandas 管表 → numpy 算信号(发动机算得快)→ scipy 做评估
策略:MA5 上穿 MA20(金叉)买入,MA5 下穿 MA20(死叉)卖出
本脚本为教学演示,不构成投资建议,今后只需要改策略就可以 —— 四大库合体就是一条完整的流水线。

导入与函数前置

from datetime import datetime # datetime:Python 自带的时间模块 import numpy as np # numpy:数值计算(发动机) import pandas as pd # pandas:表格处理(工作台) from scipy import stats # scipy.stats:统计工具(工具箱) from quant_data import get_kline # 从数据工具箱拿"拉数据"的函数

backtest_signals · 信号生成函数

核心逻辑:快线上穿慢线 → 1(持仓);快线下穿慢线 → 0(空仓);
shift(1) 防未来函数 —— 信号明天才生效。
#函数前置 def backtest_signals(df, fast=5, slow=20): """ 在表格上算出金叉/死叉信号,返回加了信号列的表格。 fast 是快速均线天数(默认 5 天),slow 是慢速均线天数(默认 20 天)。 """ close = df["close"] # 先取收盘价这一列 # 新增两列:5 日均线 和 20 日均线(rolling 滚动窗口均值) df["MA" + str(fast)] = close.rolling(fast).mean() df["MA" + str(slow)] = close.rolling(slow).mean() # "MA" + str(fast):字符串拼接,5 和 20 是函数参数传进来的数字 # 所以列名会自动变成 MA5 和 MA20,想换成 10/60 也行 # numpy 的 np.where:满足条件的位置填 1,不满足填 0 df["signal"] = np.where(df["MA" + str(fast)] > df["MA" + str(slow)], 1, 0) # 1 表示:短线在长线上方(看多,持仓);0 表示:下方(看空,空仓) # 找"信号变动"的那一天:0->1 是金叉(买入),1->0 是死叉(卖出) df["signal_change"] = np.diff(df["signal"], prepend=0) # np.diff:算前后两天的差值; # prepend=0:在最前面补一个 0 让长度对齐; # 防"未来函数":信号要"明天才生效",不能当天看信号当天买 # .shift(1):把整列往下挪一行;.fillna(0):挪出来的空位填 0 df["position"] = df["signal"].shift(1).fillna(0) return df # 返回加了新列的表格

主流程 · 四大库依次登场

① datetime 定日期 + 拉数据

if __name__ == "__main__": # ========== ① datetime 定日期 + 拉数据 ========== # end_date 用今天的日期动态生成 df = get_kline(start_date="20260101", end_date=datetime.now().strftime("%Y%m%d"))

② pandas 管表

# ========== ② pandas 管表 ========== df["收益率"] = df["close"].pct_change() # 新增一列:每日收益率

③ numpy 算信号

# ========== ③ numpy 算信号 ========== df = backtest_signals(df) # 调用上面封装的函数,算出金叉死叉信号

④ scipy 评估

# ========== ④ scipy 评估 ========== # 策略收益 = 持仓状态(0/1) × 当日收益率(持仓才赚钱,空仓不亏不赚) df["策略收益"] = df["position"] * df["收益率"] sr = df["策略收益"].dropna() # 去掉空值 # 策略夏普比率 sharpe = (sr.mean() * 252) / (sr.std() * np.sqrt(252)) if sr.std() else 0.0 t_stat, p_value = stats.ttest_1samp(sr, 0) # t 检验:策略收益是不是显著

输出 · 买卖信号 & 策略评估

print("\n===== 最近 5 次买卖信号 =====") trades = df[df["signal_change"] != 0] # 只留"信号变动"的日子 if len(trades) > 0: # 条件分支:确实有信号 recent = trades.tail(5).copy() # 取最近 5 条;.copy() 复制一份防止改坏原表 action_list = [] # 建一个空列表,用来装"动作"文字 for v in recent["signal_change"]: # 循环:一行一行看信号变动值 if v == 1: # 0 变 1 action_list.append("买入(金叉)") # 装"买入" elif v == -1: # 1 变 0 action_list.append("卖出(死叉)") # 装"卖出" else: # 其他情况 action_list.append("无") # 装"无" recent["动作"] = action_list # 把动作列表挂成新的一列 print(recent[["动作", "close"]].to_string()) # 打印动作和当日收盘价 last = trades.iloc[-1] # .iloc:按位置取行;[-1] 是最后一行 if last["signal_change"] == 1: # 判断最后一次是买还是卖 last_action = "买入(金叉)" else: last_action = "卖出(死叉)" # last.name 就是最后一行的时间(因为日期是索引) print(f"\n最近一次信号:{last.name.date()} {last_action},当日收盘 {last['close']:.2f}") else: # 没有信号的情况 print("样本期内没有出现完整信号") print("\n===== 策略评估 =====") print(f"样本期策略平均日收益 : {sr.mean() * 100:.4f}%") # 平均日收益 print(f"策略夏普比率 : {sharpe:.2f}") print(f"t 检验 p 值 : {p_value:.4f} (<0.05 表示显著)") print("\n⚠️ 金叉死叉只是最简单的教学思路,本脚本不构成投资建议")

核心要点速览

datetime:定日期
pandas:管表
numpy:算信号
scipy:做评估
金叉:MA5 上穿 MA20 → 买入
死叉:MA5 下穿 MA20 → 卖出
防未来函数:shift(1)
夏普比率:评估风险调整后收益
t 检验:判断策略是否显著
教学演示:不构成投资建议
金句: datetime 定日期, pandas 管表, numpy 算信号, scipy 做评估 —— 四大库合体,就是一条完整的流水线。

运行输出 · python .\1.py

PS> python .\1.py [数据源] akshare 不可用(ProxyError),尝试 baostock... login success! logout success! [数据源] baostock 获取成功,共 179 条 ===== 最近 5 次买卖信号 ===== 动作 close date 2026-04-21 卖出(死叉) 1379.368565 2026-07-14 买入(金叉) 1214.880000 2026-08-19 卖出(死叉) 1307.880000 2026-09-08 买入(金叉) 1309.300000 2026-09-11 卖出(死叉) 1275.160000 最近一次信号:2026-09-11 卖出(死叉),当日收盘 1275.16 ===== 策略评估 ===== 样本期策略平均日收益 : -0.0290% 策略夏普比率 : -0.48 t 检验 p 值 : 0.6898 (<0.05 表示显著) ⚠️ 金叉死叉只是最简单的教学思路,本脚本不构成投资建议
关键结论:样本期内共出现 5 次买卖信号(3 次死叉 + 2 次金叉);
策略平均日收益 -0.0290%,夏普比率 -0.48;
t 检验 p = 0.6898 ≥ 0.05,策略收益不显著 —— 金叉死叉只是教学思路,实战需谨慎。