拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python收盘复盘脚本:把“位置不破、移动防守”变成可执行规则

写收评最怕的不是行情判断错而是判断本身没有边界。比如“这个位置不破指数和个股的反弹还将延续”这句话听起来很有道理但真正落到操作上至少缺少三个可计算的部分这个位置具体是哪个价格、怎样才算“不破”、如果破了应该用什么方式防守。与其把收评写成涨跌预测不如把它变成一个可以运行的规则系统。本文从“26年8月12日股市收评这个位置不破指数个股反弹将还会延续移动跟踪防守即可”这句典型的收盘总结出发拆解其中的技术判断并用 Python 实现一个“收盘复盘脚本”输出关键位置、移动防守线和当日信号。文中的行情数据为模拟数据所有结果只用于演示规则不构成投资建议。1. 先理解收评里的“位置不破”和“移动跟踪防守”到底指什么1.1 收评的三种写法只有一种能沉淀成代码日常看到的收评大概可以分成三类。第一类是预测式例如“明天大概率上涨”“下周行情值得期待”。这类写法的问题在于没有明确边界第二天如果没涨很难说它是错了还是需要更多时间验证无法闭环。第二类是观点式例如“当前位置有支撑反弹有望延续”。这句话点出了方向但没有给出“支撑”的具体定义也没有说明什么情况下观点失效读者看完不知道该在哪个价格做决策。第三类是规则式例如“指数只要不跌破 3200 点日线反弹结构保持若收盘跌破 3200 点则离场或降低仓位并设置移动防守线”。这里有位置、有触发条件、有应对动作最关键的是它能把每个概念翻译成程序里的变量和 if 条件。写代码也好写收评也好真正有价值的是第三类。预测式观点很难验证规则式判断可以被回测、被复盘、被优化。本文要做的就是把标题里那句“位置不破、移动跟踪防守”改造成规则式判断。1.2 “这个位置不破”里的“位置”是什么技术分析里“位置”通常指支撑位或压力位。支撑位的常见来源包括前 N 日最低点均线比如 MA20、MA60密集成交平台跳空缺口边缘筹码分布峰值区域。在代码中最容易落地的是“前低”和“均线”。例如指数在一段时间内反复在 3200 点附近止跌那么 3200 就是观察位。这里要注意支撑位不是一条绝对精确的线它更像一个价格区间。程序里如果只有一个固定数值需要额外设置缓冲或判定标准。“不破”也有多种定义。日线级别最常见的三种判定方式含义优点缺点收盘价不破每日收盘价高于支撑位信号稳定过滤盘中噪音反应慢盘中大跌时无法及时提示最低价不破每日最低价高于支撑位对风险敏感能捕捉假破位容易把盘中插针误判为破位连续两日收盘不破连续两天收盘价高于支撑位过滤瞬时波动效果好延迟明显可能已经回吐较多利润实际项目中应该把“破位”和“跌破”区分开。比如盘中最低价瞬间跌破 3200但收盘又拉回 3200 上方这算不算破位不同人有不同理解。代码要支持两种模式而不是把定义写死。1.3 “移动跟踪防守”是防守线的移动不是固定止损固定止损的思路是买入后设置一个固定比例比如亏 5% 就离场。价格涨到 10% 时止损线还是亏损 5% 的位置这会导致利润大幅回吐。移动止损的思路正好相反随着价格不断走高把防守线也逐步上移让防守线始终跟住当前价格。比如最高涨到 100 元按回撤 5% 计算防守线就是 95 元后来最高涨到 110 元防守线自动上移到 104.5 元。这样做的好处是不需要猜测顶部在哪里只要价格不跌回防守线就默认趋势还在一旦跌破防守线说明反弹结构可能改变立刻执行防守动作。收评中的“移动跟踪防守即可”本质上就是这么一套规则。它不是预言价格会涨到哪而是告诉读者只要防守线不被跌穿就继续按反弹思路跟踪跌穿了就切换到防守状态。2. 先把行情数据变成 DataFrame再定义关键位置2.1 准备数据模拟数据与真实行情接口在写策略之前先要有一套“收盘数据”。最方便的方式是构造一个带开盘价、最高价、最低价、收盘价和成交量的 DataFrame。import pandas as pd import numpy as np # 模拟数据仅用于演示不代表真实行情 trade_days pd.date_range(2026-07-28, periods12, freqB) mock { open: [3190, 3204, 3208, 3216, 3222, 3228, 3236, 3248, 3252, 3260, 3274, 3282], high: [3206, 3215, 3224, 3223, 3238, 3241, 3254, 3263, 3261, 3275, 3286, 3294], low: [3185, 3198, 3202, 3208, 3212, 3220, 3228, 3238, 3244, 3252, 3262, 3270], close: [3201, 3210, 3218, 3217, 3232, 3235, 3249, 3257, 3256, 3270, 3280, 3288], volume: [10000, 12000, 11000, 9000, 13000, 14000, 15000, 12500, 11800, 16000, 17000, 18000], } df pd.DataFrame(mock, indextrade_days) print(df.tail())这里用了 12 个交易日的数据日期从 2026-07-28 到 2026-08-14其中一定包含 8 月 12 日这个示例日期。真实项目中不要用模拟数据跑结论应该接行情数据源比如 akshare、tushare 或自建数据库并且要确认接口版本、返回字段和复权方式。2.2 关键支撑位的自动计算前低和均线支撑位可以由程序自动计算也可以手工预设。手工预设适合“某个整数关口”但扩展性差所以代码里先计算常见的两个指标。# MA5 只用于演示真实日线策略一般看 MA20、MA60 df[ma5] df[close].rolling(5).mean() # 前 5 日低点shift(1) 表示不包含当天 df[prior_low_5] df[low].shift(1).rolling(5).min() print(df[[close, ma5, prior_low_5]])重点说明一下rolling的窗口含义。rolling(5).min()会取最近 5 根 K 线的最低价但如果不加shift(1)就会把当天最低价也包含进去这在收盘复盘时会产生“收盘后才知道当天最低价”的问题。复盘场景下当天数据已经确定所以用不用shift(1)取决于你是否希望支撑位包含当日极值。如果希望支撑位更稳定可以用 MA20如果希望更贴近近期波动可以用 MA10 或前低。不要同时塞太多指标先从单一规则跑通。2.3 把“不破位”写成可计算的判断列有了收盘价、最低价和支撑位之后就可以生成两个布尔列一个表示“最低价是否守住支撑”另一个表示“收盘价是否守住支撑”。support 3200 # 手工预设关键位置也可以取前低或均线值 df[above_support] df[low] support df[close_above_support] df[close] support # 自定义收盘和最低价都不破才算“位置守住” df[hold_structure] df[close_above_support] df[above_support] print(df[[close, low, above_support, close_above_support, hold_structure]])这里的support是写死的后续可以改成动态计算比如df[prior_low_5]或df[ma5]。用固定数值的好处是逻辑简单坏处是市场波动变大后固定位置可能很快失效。生产环境建议把支撑位来源抽象成函数。3. 实现移动跟踪防守从“最高点回撤”到“动态防守线”3.1 移动防守线的计算公式移动防守线最朴素的做法是从入场后出现的最高价往下回撤固定比例。trail_pct 0.03 # 最高价回撤 3% 触发防守 # 直接基于全样本 cummax 的写法只适合无中途离场的简单场景 df[max_high] df[high].cummax() df[trail_stop] df[max_high] * (1 - trail_pct) print(df[[high, max_high, trail_stop]])cummax会从样本第一根 K 线开始累计最高价所以如果中间发生过止损离场再用这个全局最高价计算防守线就不准确。更合理的做法是在持仓循环中单独维护一个变量只有持仓期间的最高价才能决定防守线。3.2 生成买入、持有和离场信号下面这段代码用逐根 K 线遍历的方式处理状态。空仓时如果收盘价站上关键位置就买入持仓时如果最低价跌破移动防守线就离场。position 0 entry_price 0.0 max_price_after_entry 0.0 trail_stop 0.0 signals [] for idx, row in df.iterrows(): if position 1: max_price_after_entry max(max_price_after_entry, row[high]) trail_stop max_price_after_entry * (1 - trail_pct) # 最低价跌破防守线按防守线离场 if row[low] trail_stop: exit_price trail_stop signals.append((idx, sell, exit_price)) position 0 max_price_after_entry 0.0 trail_stop 0.0 continue if position 0 and row[close] support: entry_price row[close] position 1 max_price_after_entry row[high] trail_stop max_price_after_entry * (1 - trail_pct) signals.append((idx, buy, entry_price)) print(交易信号) for sig in signals: print(sig)这段代码有两个地方需要结合真实场景理解。第一离场按trail_stop价格成交是理想化假设。实际盘中跌破防守线时卖出价格大概率低于防守线所以回测结果会偏乐观。第二同一根 K 线内先触发离场、后面又满足买入条件时程序用continue跳过了同一天的买入。这是避免在同一天反复交易但也会漏掉一些真实可能出现的入场机会。实盘策略通常会在日线级别把交易信号放在次日执行而不是当天收盘立即执行。3.3 把信号翻译成收评文本程序算出的是布尔值和数字收评要的是人能直接读懂的句子。到这一步可以把最新状态格式化输出。latest df.iloc[-1] current_stop df[trail_stop].iloc[-1] status 持有 if position 1 else 空仓 print(收盘复盘输出) print(f关键位置{support}) print(f最新收盘{latest[close]:.2f}) print(f最新最低价{latest[low]:.2f}) print(f移动防守线{current_stop:.2f}) print(f当前状态{status})输出结果类似收盘复盘输出 关键位置3200 最新收盘3288.00 最新最低价3270.00 移动防守线3195.18 当前状态持有这里“持有”的含义是价格仍在关键位置上方且没有跌破移动防守线。它不代表明天一定上涨只代表当前规则没有给出离场信号。4. 做个简单回测验证“不破就持有、破了就防守”是否可行4.1 用资金曲线做一次最小回测回测的目的不是证明策略很赚钱而是验证规则在历史数据上是否会按预期产生买卖。最小回测可以用一个简单的资金账户模拟。cash 100000.0 hold_num 0 buy_price 0.0 max_high 0.0 stop_line 0.0 records [] df[strategy_value] np.nan for idx, row in df.iterrows(): if hold_num 0: max_high max(max_high, row[high]) stop_line max_high * (1 - trail_pct) if row[low] stop_line: cash hold_num * stop_line records.append((sell, idx, stop_line)) hold_num 0 max_high 0.0 stop_line 0.0 if hold_num 0 and row[close] support: buy_price row[close] hold_num cash / buy_price cash 0.0 max_high row[high] stop_line max_high * (1 - trail_pct) records.append((buy, idx, buy_price)) df.loc[idx, strategy_value] cash hold_num * row[close] print(交易记录) for rec in records: print(rec) total_return df[strategy_value].iloc[-1] / 100000.0 - 1 print(f策略总收益{total_return:.2%})这里用“现金 持仓市值”计算每个交易日的策略净值。买入时用全部现金卖出时把持仓按防守线价格换成现金。4.2 为什么回测结果不能直接当成预期收益上面的回测没有包含以下因素手续费印花税滑点T1 限制停牌和涨跌停无法成交入场与离场信号发生在同一根 K 线的处理。任何一个因素都足以明显改变结果。比如 A 股当日买入不能当日卖出如果买入信号出现在盘中当天无法验证是否成功成交需要额外逻辑处理。因此最小回测只适合验证规则是否正确不适合用来估计未来收益。另一个常见问题是样本量。10 多根 K 线只能算一个演示片段连统计数据都谈不上。真实项目里做回测至少要用几年日线数据并且要做滚动样本外测试。4.3 胜率和盈亏比要成对统计如果需要统计胜率不能只看最后的资金曲线而要把买卖配对。buy_prices [] sell_prices [] for rec in records: if rec[0] buy: buy_prices.append(rec[2]) elif rec[0] sell and len(buy_prices) len(sell_prices): sell_prices.append(rec[2]) profits [] for bp, sp in zip(buy_prices, sell_prices): profits.append(sp / bp - 1) if profits: win_rate sum(1 for p in profits if p 0) / len(profits) avg_profit sum(profits) / len(profits) print(f交易笔数{len(profits)}) print(f胜率{win_rate:.2%}) print(f平均单笔收益率{avg_profit:.2%}) else: print(交易笔数不足无法统计胜率)胜率不是唯一指标。一笔赚 20%、两笔亏 5%胜率只有三分之一但策略仍然可能盈利。关键要看盈亏比和交易次数是否足够。5. 常见问题为什么算出来的防守位总和你理解的不一样5.1 防守线跳来跳去或者一直不动现象防守线有时突然跳到很低的位置有时又长时间不变。常见原因是cummax()在全样本上累计了历史最高点。如果程序在中途离场后又重新入场离场前的最高价仍然参与计算导致新持仓的防守线被顶得很高策略变得非常迟钝。正确做法是在每次入场时重置max_price_after_entry只累计当前持仓期间的最高价。# 错误示范全样本累计最高价 df[trail_stop] df[high].cummax() * (1 - trail_pct) # 正确思路持仓期间单独维护变量 if position 1: max_price_after_entry max(max_price_after_entry, row[high]) trail_stop max_price_after_entry * (1 - trail_pct)5.2 盘中明明破位了程序却没有给卖出信号现象最低价已经跌破防守线但程序输出“持有”。原因多半是判断条件用了收盘价或者只检查了close stop_line。盘中破位和收盘破位是两种不同定义代码里要显式选择。# 敏感型最低价破位立即离场 if row[low] trail_stop: ... # 稳健型收盘价破位才离场 if row[close] trail_stop: ...实盘建议至少同时输出两种模式的结果再决定用哪一种。敏感型容易在震荡行情中反复止损稳健型又可能让利润回吐更多。没有绝对优劣只有是否匹配自己的风险偏好。5.3 除权除息导致价格跳空支撑位和防守线失效股票分红送转后价格会向下跳空。如果用的是未复权数据昨天收盘价 20 元今天可能变成 15 元支撑位和均线都会被突然拉低。这样算出来的防守线不仅没有参考价值还会产生错误信号。解决方法是使用“前复权”数据。前复权会以当前价格为基准把历史价格按除权因子调整让价格曲线保持连续。接入行情接口前一定要确认返回的是不是前复权数据以及复权基准是哪一天。5.4 历史数据明明支撑住了程序却反复止损现象价格在支撑位附近多次下探程序每次都触发离场但价格后面又涨回来了。这可能不是程序错误而是参数和判定粒度的问题。固定百分比trail_pct 0.03对波动小的品种可能太紧对波动大的品种可能太松。可以考虑用 ATR 代替固定百分比让防守线随市场波动自动调整。# 简单 ATR 近似算法 df[tr] np.maximum( df[high] - df[low], np.maximum( abs(df[high] - df[close].shift(1)), abs(df[low] - df[close].shift(1)) ) ) df[atr] df[tr].rolling(14).mean() df[trail_stop_atr] df[max_high] - 2 * df[atr]这种写法仍然偏简化真正的 ATR 通常用 Wilder 平滑算法。先理解思路再根据自己熟悉的方式实现。6. 从“收评脚本”到“每日自动复盘”生产环境还缺什么6.1 学习环境与生产环境的差距学习环境里手动运行脚本、打印输出就够了。生产环境要处理的远不止“策略逻辑是否正确”还包括数据稳定性、调度、通知、存储和异常恢复。维度学习环境生产环境数据模拟数据或本地 CSV行情接口、数据库需要异常重试调度手动运行cron、APScheduler 或任务平台通知print 输出邮件、企业微信机器人、IM 通知存储不落库sqlite、MySQL、ClickHouse日志控制台文件日志、日志采集、告警权限本机运行密钥管理、接口权限、只读账号风控未考虑仓位限制、连续止损熔断合规可以随意写明确风险提示不承诺收益6.2 定时运行先用 cron 跑起来如果只在收盘后跑一次cron 是最简单的方案。假设脚本路径是/data/review/review.py每个交易日 15:10 执行10 15 * * 1-5 cd /data/review /usr/bin/python3 review.py review.log 2115:10是为了给数据源留出更新缓冲。如果数据接口更新延迟可以把时间调晚一点。日志必须重定向到文件否则 cron 执行失败时看不到报错。也可以把每日结果写到 sqlite方便后续复盘。import sqlite3 conn sqlite3.connect(review.db) df.tail(1)[[close, ma5, trail_stop]].to_sql( daily_review, conn, if_existsappend, indexTrue ) conn.close()注意to_sql在不同 pandas 版本下对 sqlite3 连接的支持不完全一样。如果遇到报错可以改用 SQLAlchemy 的create_engine(sqlite:///review.db)再调用to_sql。6.3 输出自动收评时风险提示不能省自动化脚本一旦接入 IM 机器人输出就会直接触达读者。此时不能只发“当前状态持有”还要附上规则说明和风险提示。例如收盘复盘 关键位置3200 最新收盘3288 移动防守线3195 状态持有 说明以上结果为规则计算输出仅用于技术复盘不构成投资建议。不要把“持有”包装成“必涨”也不要把“离场”解读成“马上暴跌”。自动化工具的价值是稳定执行规则而不是替用户做收益承诺。7. 写收评前可以套用的技术清单7.1 收评复盘清单每次写收评或调试复盘脚本时对照这张表检查一遍可以减少规则不明确的问题。检查项说明行情周期是日线、60 分钟还是周线不同周期支撑位意义不同支撑位来源前低、均线、平台、筹码区还是手工预设破位判定收盘破位、盘中破位还是连续 N 日破位防守线算法最高价回撤、ATR 跟踪、均线跟随参数周期MA20 还是 MA60回撤 3% 还是 5%信号动作买入、持有、减仓、离场动作要明确输出格式人读文本要包含位置、价格、状态和风险提示复盘归档每天结果落库事后能检查规则是否有效7.2 下一步可以扩展的方向从当前最小闭环出发可以按顺序做四件事。第一把固定支撑位改成动态支撑位。用前低或均线替代写死的 3200规则就不再依赖人工设定。第二加入成交量过滤。反弹延续的可靠性往往和量能有关缩量上涨时可以把防守线收紧放量突破时可以把防守线放宽。第三引入多周期判断。日线判断方向60 分钟线寻找更精确的防守位置能减少单周期噪音。第四做历史信号归档和滚动回测。把每天产生的信号存下来定期统计胜率、盈亏比和最大回撤再用更新的数据做样本外测试。7.3 对新手最值得做的一个练习如果你刚开始接触这类复盘脚本不要急着写复杂策略。先用模拟数据跑通“支撑位判断 移动止损”这一最小流程然后接真实历史数据最后再考虑自动化。最应该坚持的习惯是每一条收评都必须能回答三个问题——关键位置是多少、怎么算破位、破位之后防守线在哪。回答不了这三个问题文字写得再流畅也不是一份合格的技术复盘。收盘复盘的核心不是预测明天涨还是跌而是把“反弹还能不能延续”转成“位置有没有守住、防守线有没有被跌破”两个可判断的问题。脚本能自动跟踪人只需要在规则被触发时做出正确响应。能做到这一点收评就不再是一句口号而是一套可以反复验证的工程方法。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门