华笙软件量化选点实战:从多因子分析到可复现选点流程
简介这份资源围绕华笙软件的分析与选点展开面向从事电子制造、治具制作与电路测试的工程师及技术人员重点解决EzGrid与siland等工具在治具设计、四线测量中的功能评估与版本选型问题。压缩包共2562个文件约22.28MB以1655个gif界面图示、299个htm帮助页、187个jpg截图为主辅以ini、cnf等配置文件exe可执行程序与dll动态库以及drl钻孔、art、apr等工艺数据文件构成较完整的软件运行与文档体系。已有1194人学习下载。读者可从中获取软件功能模块的实际界面与配置样例对照不同版本评估自定义行列、数据绑定、排序过滤等特性并结合四线测量相关数据文件理解治具制作中的精度控制与流程衔接为工具选型和二次开发提供参考。1. 华笙软件的分析与选点一套量化选点流程到底怎么落地第一次听到“华笙软件的分析与选点”很多人会以为是某个行情软件里点几下就能出结果的按钮。实际做过一轮完整选点的人都知道真正耗时间的不是“选”而是“分析”这一步怎么被拆成可复现的规则。我见过太多人把选点做成拍脑袋看几个指标、翻两根K线、凭感觉定一个入场位回测一跑全是幸存者偏差。华笙软件这套东西的价值恰恰在于它把“分析”和“选点”拆成了两段可验证的流程——先用规则把候选池筛出来再用参数把入场点定死。这篇笔记面向的是手里有行情数据、想自己搭一套选点流水线的从业者新手能照着步骤跑通熟手能看清参数边界和翻车点。2. 华笙软件的分析层候选池是怎么被筛出来的分析层解决的是“从几千个标的里留下几十个”的问题。这一步做不干净后面的选点再精细也是在垃圾堆里挑金子。华笙软件的分析逻辑本质上是多因子过滤但它的工程价值在于把因子计算、阈值判定、结果落库串成了一条可重跑的链路而不是每次手动导表。2.1 分析层的三个输入行情、因子、阈值任何选点系统的分析层都逃不开三样东西干净的行情数据、定义明确的因子、可调的阈值。华笙软件在这块的常见做法是把三者分离——行情走独立的数据同步任务因子走计算脚本阈值走配置文件。这样改阈值不用动代码换因子不用重拉数据。行情数据我一般要求至少包含开高低收、成交量和复权因子四列缺复权因子的数据在跨除权日时会把因子算歪。因子层面分析层常用的有动量类N日涨幅、波动类ATR或标准差、流动性类成交额均值、趋势类均线斜率。阈值则是每个因子一个上下界比如动量要求近20日涨幅在5%到30%之间太低没动能太高是追高。下面这段是分析层最小可跑的因子计算与过滤脚本用pandas实现逻辑上等价于华笙软件分析层的核心动作import pandas as pd import numpy as np def build_factors(df, window20): # df 需含 date, code, open, high, low, close, volume, adj_factor df df.sort_values([code, date]).copy() # 后复权收盘价避免除权日跳空污染因子 df[close_adj] df[close] * df[adj_factor] g df.groupby(code)[close_adj] # 动量因子近 window 日涨幅 df[momentum] g.pct_change(window) # 波动因子近 window 日收益率标准差 df[ret] g.pct_change(1) df[volatility] df.groupby(code)[ret].transform( lambda s: s.rolling(window).std()) # 流动性因子近 window 日成交额均值万元 df[amount] df[close] * df[volume] / 1e4 df[liq] df.groupby(code)[amount].transform( lambda s: s.rolling(window).mean()) # 趋势因子20日均线斜率归一化 df[ma20] g.transform(lambda s: s.rolling(20).mean()) df[slope] df.groupby(code)[ma20].transform( lambda s: s.diff(5) / s.shift(5)) return df def filter_pool(df, cfg): # cfg 为阈值字典逐条过滤保留全部条件同时满足的标的 mask ( df[momentum].between(cfg[mom_low], cfg[mom_high]) (df[volatility] cfg[vol_max]) (df[liq] cfg[liq_min]) (df[slope] cfg[slope_min]) ) return df[mask].dropna(subset[momentum, volatility, liq, slope])这段代码的关键不在语法而在几个容易翻车的细节。adj_factor必须在计算前就乘上去否则除权日当天动量会凭空跳变十几个点筛出来的全是刚除权的票。rolling(window)在每只票的序列上独立滚动用groupby().transform()而不是直接rolling()是为了防止跨标的串行。dropna放在最后是因为滚动窗口前期必然有空值提前drop会把整段历史砍掉。参数上window20对应约一个月的交易日是动量类因子的常用起点mom_low0.05、mom_high0.30这组阈值的意思是“要有动能但别追高”实际调的时候建议先用历史分位数看一眼分布别拍脑袋定。vol_max控制波动上限太高的票选点容错率极低liq_min是流动性底线低于这个值的票滑点会吃掉大部分利润。2.2 阈值不是拍出来的用分位数定边界新手最容易犯的错是把阈值当成固定常数一套参数用三年。市场结构变了同样的5%动量阈值在低波年份筛不出票在高波年份筛出一堆妖股。华笙软件分析层比较稳的做法是用滚动分位数动态定阈值。具体操作是对每个因子取过去250个交易日的全市场分布用60%和90%分位作为上下界。这样阈值随市场自适应低波年份自动放宽高波年份自动收紧。实现上就是在filter_pool之前插一步def dynamic_threshold(df, factor, low_q0.6, high_q0.9, lookback250): # 按日计算全市场分位数再向后填充到每只标的 daily df.groupby(date)[factor].quantile([low_q, high_q]).unstack() daily.columns [low, high] daily daily.rolling(lookback, min_periods60).mean() return df.merge(daily, left_ondate, right_indexTrue, howleft)这里min_periods60是给冷启动留的缓冲历史不足250天时用已有数据算别直接返回空。rolling().mean()是对分位数本身再做平滑防止单日极端行情把阈值拉爆。这套动态阈值我在几个不同市场周期的数据上跑过比固定阈值的候选池稳定性明显好一截代价是冷启动阶段前两个月结果不可信得等窗口填满。2.3 分析结果的落库与可复现分析层跑完必须落库不能只在内存里过一遍。华笙软件这类系统的工程要求是每次分析产出带日期戳的候选池快照字段至少包含标的代码、各因子值、是否入选、入选日期。这样后面选点出问题时能回溯到“当时为什么选它”。落库用最简单的SQLite就够别一上来就上重型数据库import sqlite3 def save_pool(df, db_pathpool.db, trade_date2024-01-01): cols [code, momentum, volatility, liq, slope] out df[cols].copy() out[trade_date] trade_date con sqlite3.connect(db_path) out.to_sql(candidate_pool, con, if_existsappend, indexFalse) con.close()if_existsappend保证历史快照不被覆盖这是回测可复现的前提。字段只存因子原始值和入选标记不存衍生结论衍生逻辑留在代码里改逻辑不用改历史数据。这一步看着琐碎但它是后面排查“为什么某天选点全错”的唯一后悔药。3. 选点层从候选池到具体入场位分析层给的是“哪些票值得看”选点层解决的是“在哪一天、哪个价位进”。这两层混在一起做是很多自建系统的通病——筛选和择时耦合回测时根本分不清亏损是选错了票还是进错了点。华笙软件把选点独立出来的意义就在这。3.1 选点的三种触发方式与选型理由选点触发常见三类突破触发、回调触发、时间触发。突破触发是价格站上某个关键位前高、均线上方一定幅度才入场优点是顺势、假信号相对少缺点是在震荡市里频繁被扫。回调触发是等价格回踩到支撑位再进成本低但容易接飞刀。时间触发是候选池入选后固定第N天入场最简单但完全放弃择时。我一般会根据候选池的因子构成来选动量因子主导的池子配突破触发因为动量票的特点是强者恒强等回调往往等不到波动收敛型池子配回调触发因为这类票本身就在区间内晃。华笙软件的选点模块通常把这几种触发做成可插拔的策略配置里选一种而不是写死。下面是一个突破触发的选点实现逻辑是收盘价站上20日高点且成交量放大def breakout_signal(df, lookback20, vol_ratio1.5): df df.sort_values([code, date]).copy() g df.groupby(code) # 前 lookback 日最高价不含当日避免未来函数 df[hh] g[high].transform( lambda s: s.shift(1).rolling(lookback).max()) # 当日成交量相对近20日均量的倍数 df[vol_ma] g[volume].transform( lambda s: s.shift(1).rolling(20).mean()) df[vr] df[volume] / df[vol_ma] # 触发条件收盘突破前高 且 放量 df[signal] (df[close] df[hh]) (df[vr] vol_ratio) return df[df[signal]]shift(1)是这段代码的命门。前高如果用当日最高价算收盘价永远不可能超过当日最高价信号恒为假更隐蔽的错误是用rolling().max()不shift等于把当日也算进前高回测时看着能触发实盘永远触发不了这是典型的未来函数翻车。vol_ratio1.5是放量门槛低于1.2基本等于没放量高于2.0会漏掉大量温和突破1.5是个折中起点。3.2 入场价与止损位怎么定死选点信号出来只是“该进了”入场价和止损位必须提前定死不能盘中临时改。常见做法是次日开盘价入场止损设在突破位下方一个ATR。华笙软件选点层比较规范的处理是把入场价、止损价、目标价作为信号的一部分一起算出来落库。def entry_plan(sig_df, atr_mult1.5): out sig_df.copy() # 次日开盘入场回测时用次日open实盘用集合竞价参考价 out[entry] out.groupby(code)[open].shift(-1) # 止损突破位前高下方 atr_mult 倍 ATR out[atr] out.groupby(code)[close].transform( lambda s: s.diff().abs().rolling(14).mean()) out[stop] out[hh] - atr_mult * out[atr] # 目标按 2:1 盈亏比反推 out[target] out[entry] 2 * (out[entry] - out[stop]) return out.dropna(subset[entry, stop, target])shift(-1)取次日开盘回测里这是合法的因为信号在当日收盘后产生次日开盘执行但如果你在实盘里用当日收盘价当入场价那就是自欺欺人。atr_mult1.5控制止损宽度太小容易被正常波动扫掉太大单笔亏损过重1.5到2.0是常见区间。盈亏比2:1是起点不是教条胜率高的策略可以降到1.5:1胜率低的必须往上提。3.3 选点结果与候选池的关联校验选点信号必须能关联回候选池否则你无法回答“这个信号是不是分析层选出来的票”。工程上就是一次join但校验逻辑要写清楚信号日必须在候选池入选日之后的一定窗口内超出窗口的信号视为过期。def link_pool(signals, pool, max_gap5): # pool 含 code, trade_datesignals 含 code, date merged signals.merge( pool.rename(columns{trade_date: pool_date}), oncode, howinner) merged[gap] (pd.to_datetime(merged[date]) - pd.to_datetime(merged[pool_date])).dt.days # 只保留入选后 max_gap 个自然日内的信号 return merged[(merged[gap] 0) (merged[gap] max_gap)]max_gap5的意思是候选池入选后一周内没触发就作废防止拿一个月前的池子配今天的信号。gap 0排除信号早于入选的脏数据这种脏数据在手工导表时特别常见join完不校验就会把未来信息混进回测。4. 华笙软件选点流程的避坑与排查这套流程跑通不难跑对很难。下面五条是我在实际搭类似系统时反复踩过的坑每条都按现象、原因、解决写清楚。4.1 回测漂亮实盘拉胯先查未来函数现象回测年化很好看实盘一上就亏。原因九成是未来函数——用了当日收盘价算信号又用当日收盘价成交或者前高没shift把当日算进去。解决把所有rolling().max()、rolling().mean()前面加shift(1)成交价统一用次日开盘然后拿信号日当天的数据手工核对一遍看信号是不是真的在收盘后才知道。4.2 候选池某天突然空了复权因子断档现象某一天候选池数量从几十个掉到零。原因通常是复权因子在那天缺失或为0导致close_adj算出来是NaN所有因子跟着变空。解决在因子计算前加一步校验adj_factor为空或小于等于0的行直接剔除并记日志别让它静默污染下游。4.3 选点信号集中在少数几只票流动性阈值太低现象信号分布极不均匀八成信号来自三五只票。原因是liq_min设太低小盘票成交额小、波动大容易反复触发。解决把流动性阈值提到全市场成交额的中位数以上或者对单只票的信号数量设上限防止组合过度集中。4.4 止损频繁被扫ATR倍数和入场价不匹配现象止损被打掉的单子里很大一部分当天就反弹回去。原因是止损宽度按突破位算但入场价在突破位上方较远实际止损距离被压缩。解决止损改成按入场价减atr_mult * atr算而不是按突破位算保证每笔交易的风险敞口一致。4.5 动态阈值冷启动期结果不可信现象系统刚上线前两个月选点质量明显差。原因是滚动分位数窗口没填满min_periods放得太松导致早期阈值失真。解决冷启动期直接用固定阈值兜底等窗口填满再切动态阈值切换点记在配置里别让两套逻辑悄悄混跑。5. 把选点做成可验证的闭环一个参数敏感性检查技巧选点系统最怕的不是亏钱是不知道为什么会亏。华笙软件这套分析加选点的结构真正的价值在于它天然支持参数敏感性检查——同一套逻辑换几组参数跑一遍看结果稳不稳。稳的参数组合才敢上实盘一换参数就崩的说明逻辑本身在拟合噪声。具体做法是网格扫描关键参数但别全网格扫只扫最影响结果的两三个。以突破选点为例lookback和vol_ratio是最敏感的两个各取三档做九组组合import itertools results [] for lb, vr in itertools.product([10, 20, 30], [1.2, 1.5, 2.0]): sig breakout_signal(df, lookbacklb, vol_ratiovr) plan entry_plan(sig) # 简化绩效按 2:1 盈亏比统计命中率 hit ((plan[target] - plan[entry]) / (plan[entry] - plan[stop])).mean() results.append({lookback: lb, vol_ratio: vr, signals: len(plan), avg_rr: round(hit, 3)}) res_df pd.DataFrame(results).sort_values(avg_rr, ascendingFalse) print(res_df)这段代码的重点不是绩效算法多精确而是看signals数量和avg_rr的分布。如果九组里只有一组信号多且盈亏比高其余全崩那这组大概率是过拟合别用。如果相邻参数比如lookback从20到30结果平滑过渡说明逻辑有稳健性。我一般要求至少三组相邻参数的结果在同一量级才认为这个选点逻辑站得住。参数敏感性检查还有一层用法拿它当回归测试。每次改因子或改阈值逻辑重跑这九组结果和上一版对比差异超过阈值就人工看一遍。这套习惯救过我一次——某次改流动性因子时不小心把单位从万元改成元九组结果全变敏感性表一眼就看出来了不然要等实盘亏了才发现。最后说个我自己的习惯任何选点逻辑上线前先拿最近一年的数据做一次“盲跑”——把最后三个月的数据藏起来用前九个月调参再用藏起来的三个月验证。藏起来那段如果和调参段表现差太多说明参数在拟合宁可不用。这个习惯比任何回测指标都实在希望帮到你。本文还有配套的精品资源点击获取