量化交易系列—— 回测一年翻8倍的首板战法、一进二战法详解(附带具体的B/S点记录)
合规说明:本文章为个人观点,仅供用户学习参考使用,切勿当作你的决策依据。文中提到的任何个股或思路,均不构成投资建议。假设你是量化用户,该量化思想仅供你个人使用,切勿传播。
前言:本文章使用掘金量化终端进行交易回测。后续附带的部分量化操作源码为C# 代码,掘金支持C#、Python、C++、Matlab进行量化开发,知道思路以后,客官可以自行改造成你熟悉的语言进行开发尝试。如果你用的是QMT客户端,同样也可以自行更换数据源和获取数据并做处理,然后按照类似思路开发你自己的代码。当然,如果你没有量化账号进行操作,本文的首板战法、一进二,同样值得你当作参考。
备注:本文的首板战法,特指打板。前面有一篇文章,写的首板挖掘,这个注重埋伏,是两个完全不同的思想体系。

下图是我在一个之前创建的量化群里面做了个简单的文字直播,测试回测效果的。5W到42W,2024年五月中旬到2025年年初的量化回测战绩,实际上并没有一年。那时候没做文本记录,所以下面提供的详细买卖点记录,是选了个更早的,2023年5月中旬开始,到12月初的,半年不到,5W到20W,选择这个区间是因为大A长期是处于这样一个震荡行情,所以效果明显一点,并且会选择其中的个别特殊案例进行实盘解说。

本量化操作的一个初始化配置如下:
1、初始设置本金:5W
2、仓位控制:最多持仓3只票,持仓数量根据资金变化情况自动计算。简单计算规则:买入手数=(int)资金总量/3/100/11
3、打板时候,挂单计算当前排单量,每次分笔数据进行减掉成交量; 如果小于0,代表可以买进,同时记录B点; 如果到尾盘,统计的成交量都没有前面挂单时候的封板量多, 说明当天排单失败,撤销挂单。
4、卖出时候,如果当前是跌停板,则不进行卖出,模拟跌停板出不去的情况。
5、选股只选20元以内的个股.如果第二天打板,买入价格有可能超过20元。
6、选股特殊规则:文章下方付费内容进行详细解说。毕竟精彩部分,不能白白送人。
7、每天定时8点20进行选股,符合第六条特殊条件的个股,加入监控池。 监控池内的个股,当天开盘会实时收到tick分笔数据推送过来。 根据tick数据进行判断上板先后,只打第一个上板的票; 挂单委托成功以后,取消订阅其他监控池内的股票。
8、卖出规则,当天涨幅低于前一天收盘价2%,直接止盈止损。 如果大于2%,尾盘下午14点25左右没有涨停,同样进行止盈止损。 9、交易佣金,预设万分之1.2,这个是大部分人的一个可能的佣金率。我本人是万分之0.8 其他费用:如果交易佣金不足5元,按照5元扣除 印花税:卖出计算印花税,按照千分之一扣除 过户费等:占比太低,此处忽略不计了。
tick分笔数据监听与打板规则源码解析:
掘金OnTick函数可以用于监听分笔数据,也就是监控哪些个股,它会实时对分笔数据进行推送。监听分笔数据,普通用户5档;L2用户可以监听10档。L2有些券商只对机构用户,或者资金500W以上用户提供,专业投资者可以尝试;普通投资者机器和网络不一定承接得住L2数据(还包括实时逐笔数据)
9点24之前不操作,已经有3只持仓也不进行打板。

public override void OnTick(Tick tick) { if ((tick.createdAt.Hour <= 9 && tick.createdAt.Minute < 24)) { // 集合竞价未结束不打 return; }
if (!hasBuyList.Any(x => x.symbol == tick.symbol) && !hasBuyList.Any(x=>x.time.Date==tick.createdAt.Date)) { // 打板 if (hasBuyList.Count() >= 3) { // 当前持仓大于等于3只,不打 return; }
9点24分50秒起,符合一字板条件的,准备打板。打板需要额外判断是否符合特殊条件。该条件是首板打板的关键。如果符合,就会加入已购集合内,并且状态为排单状态 state=2;这个集合是自定义的类。

if (tick.createdAt.Hour == 9 && tick.createdAt.Minute == 24 && tick.createdAt.Second > 50) { // 一字打板 if (tick.quotes[0].askVolume == tick.quotes[0].bidVolume && tick.quotes[1].askVolume == 0 && tick.quotes[1].bidVolume * tick.price > 10000000) { if (_historySymbol.symbol.Any(x => x.Key == tick.symbol)) { // 特殊取值操作,此处不做展示 if (特殊条件,此处不做展示) { // 计算所需购入的手续费 var buyMoney = buyNumber * tick.quotes[0].bidPrice * 100; var handPrice = buyMoney * 0.00012; if (handPrice < 5) { handPrice = 5; // 不足5元按照5元计算手续费 }
if (buyMoney + handPrice > totalMoney) { return; // 现金余额不足,不打 } hasBuyList.Add(new HasBuyState() { hands = buyNumber, price = tick.quotes[0].bidPrice, symbol = tick.symbol, time = tick.createdAt.Date, lastClose = tick.price, bigMoney = tick.quotes[1].bidVolume, limitVol = tick.quotes[1].bidVolume, state = 2, realVolState = 1 }); AnsiConsole.MarkupLine($"【{tick.createdAt}】:委托挂单排板: {tick.symbol} 价格:{tick.price:F2} "); Log.Information($"【{tick.createdAt}】:委托挂单排板:股票代码: {tick.symbol} 挂单价格:{tick.price:F2} 挂单手数:{buyNumber}"); string[] sbs = _historySymbol.symbol.Keys.ToArray(); // 当天监听的所有个股列表 foreach (string s in sbs) { if (!hasBuyList.Any(x => x.symbol == s)) { Unsubscribe(s, "tick"); // 不在已购列表的,进行退订 } } }
} } return; }
已购持仓实体类定义如下:

尾盘不进行打板操作,因为尾盘偷鸡上板的,大多数都是坑,宁可错过也不要买。尾盘定义:下午两点半开始。并且集合竞价以后。25分到30分之间,不进行处理,这部分可以挂单,但是不会成交,所以不会有分笔成交出现,直接过滤。

除了以上特殊时间外,其他正常交易时间,如果涨停,则直接挂单。挂单以后,对其他监听的未挂单个股进行退订,这样就不会继续收到其他个股分笔数据了,因为一天只买一只的目的达成了。

如果已经购买,但是还没成交的,在排单期间,后续的每次分笔推送,都会进行判断成交量,封单量累计减掉成交量直到小于0,就代表你打板买进去了。

判断出货,详情可见下方源码解释:

else if (hasBuyList.Any(x => x.symbol == tick.symbol && x.state == 0 && tick.createdAt.Date != x.time.Date)) { // 次日,判断是否卖出 var buyed = hasBuyList.Where(x => x.symbol == tick.symbol).First(); if (tick.createdAt.Hour <= 9 && tick.createdAt.Minute < 30) { return; // 竞价不做判断 } if ((tick.createdAt.Hour == 9 && tick.createdAt.Minute >= 30) || (tick.createdAt.Hour > 9 && tick.createdAt.Hour < 15)) { if (tick.quotes[0].bidVolume == 0) { // 跌停 无法出货 return; } // 如果低于1% if ((tick.price - buyed.lastClose) / buyed.lastClose < 0.01 && (tick.createdAt.Hour < 14 || (tick.createdAt.Hour == 14 && tick.createdAt.Minute <= 25))) { // 计算出货手续费 var saleMoney = hasBuyList.FirstOrDefault(x => x.symbol == tick.symbol).hands * 100 * tick.quotes[0].bidPrice; var handPrice = saleMoney * 0.00012; if (handPrice < 5) { handPrice = 5; } handPrice += saleMoney * 0.001; // 加上印花税
GMData<Order> o = OrderValue(tick.symbol, saleMoney, OrderSide.OrderSide_Sell, OrderType.OrderType_Market, PositionEffect.PositionEffect_Close, tick.price, "量化账号ID"); if (o.status == 0) { totalMoney = totalMoney + saleMoney - handPrice;
AnsiConsole.MarkupLine($"【{tick.createdAt}】:S {tick.symbol} Price:{tick.quotes[0].bidPrice:F2} ,手续费:{handPrice:F2} 盈亏:{saleMoney - buyed.price * buyed.hands * 100 - handPrice:F2} 现金余额:{totalMoney:F2} ");
Log.Information($"【{tick.createdAt}】:S 委托成交卖出:股票代码: {tick.symbol} 成交价格:{tick.quotes[0].bidPrice:F2} 手数:{buyed.hands} 手续费:{handPrice:F2} 盈亏金额:{saleMoney - buyed.price * buyed.hands * 100 - handPrice:F2} 现金可用余额:{totalMoney:F2}");
hasBuyList.Remove(buyed); Unsubscribe(tick.symbol, "tick"); return; } else { Console.WriteLine("S 失败"); } }
尾盘出货:

// 尾盘判断,如果不是涨停板,清仓 else if (tick.createdAt.Hour == 14 && tick.createdAt.Minute > 25 && tick.quotes[0].askVolume > 0 && tick.quotes[1].askVolume > 0) { // 计算出货手续费 var saleMoney = hasBuyList.FirstOrDefault(x => x.symbol == tick.symbol).hands * 100 * tick.quotes[0].bidPrice; var handPrice = saleMoney * 0.00012; if (handPrice < 5) { handPrice = 5; } handPrice += saleMoney * 0.001; // 加上印花税
GMData<Order> o = OrderValue(tick.symbol, saleMoney, OrderSide.OrderSide_Sell, OrderType.OrderType_Market, PositionEffect.PositionEffect_Close, tick.price, "你自己的账号ID"); if (o.status == 0) { totalMoney = totalMoney + saleMoney - handPrice; AnsiConsole.MarkupLine($"【{tick.createdAt}】:S {tick.symbol} Price:{tick.quotes[0].bidPrice:F2} ,手续费:{handPrice:F2} 盈亏:{saleMoney - buyed.price * buyed.hands * 100 - handPrice:F2} 现金余额:{totalMoney:F2} ");
Log.Information($"【{tick.createdAt}】:S 委托成交卖出:股票代码: {tick.symbol} 成交价格:{tick.quotes[0].bidPrice:F2} 手数:{buyed.hands} 手续费:{handPrice:F2} 盈亏金额:{saleMoney - buyed.price * buyed.hands * 100 - handPrice:F2} 现金可用余额:{totalMoney:F2}");
hasBuyList.Remove(buyed); Unsubscribe(tick.symbol, "tick"); return; } else { Console.WriteLine("S 失败"); } }
尾盘最后一笔分笔数据推送过来进行接收,也代表当天结束了。如果这个时候排单没进去,当天打板就属于没买到。

if (tick.createdAt.Hour == 15 && tick.createdAt.Minute == 0) { var buyed = hasBuyList.FirstOrDefault(x => x.symbol == tick.symbol); if (buyed.state == 2) { buyed.limitVol -= tick.lastVolume; // 排单量递减 if (buyed.limitVol < 0) { // 尾盘竞价买入成功 // 排单成功,计算费用 // 计算所需购入的手续费 var buyMoney = buyNumber * tick.price * 100; var handPrice = buyMoney * 0.00012; if (handPrice < 5) { handPrice = 5; // 不足5元按照5元计算手续费 }
GMData<Order> o = OrderValue(tick.symbol, buyMoney, OrderSide.OrderSide_Buy, OrderType.OrderType_Market, PositionEffect.PositionEffect_Open, tick.price, "你自己的账号ID"); if (o.status == 0) //该判断仅表示函数调用无异常 { totalMoney = totalMoney - buyMoney - handPrice; // 现金余额
AnsiConsole.MarkupLine($"【{tick.createdAt}】:B {tick.symbol} Price:{tick.price:F2} 手续费:{handPrice:F2} 现金余额:{totalMoney:F2}"); Log.Information($"【{tick.createdAt}】:B 委托成交买入:股票代码: {tick.symbol} 成交价格:{tick.price:F2} 手数:{buyed.hands} 手续费:{handPrice:F2} 现金可用余额:{totalMoney:F2}"); } buyed.state = 0; buyed.lastClose = tick.price; } else { hasBuyList.Remove(buyed); AnsiConsole.MarkupLine($"【{tick.createdAt}】:委托挂单排板失败!截止收盘未排到单: {tick.symbol} "); Log.Information($"【{tick.createdAt}】: 委托挂单排板失败!股票代码: {tick.symbol} 手数:{buyed.hands} 挂单废单,撤销"); } } else { buyed.lastClose = tick.price; } }
以下是2023年5月份到2023年11月份的所有量化回测的挂单、BS点日志记录,也是本战法的买卖点(文章后面部分有完整版记录)。5W到19.9W,接近翻3倍。该战法正常情况下,可能收益不明显,处于震荡期;但是只要抓到一个,就可以让资金快速滚动。例如,以下是记录的其中一条,前面历时3个月才从5W震荡到6W多,打到一个捷荣,几天就上升到8W多。买点是2023年8月30直接排板,出货日按照前面的代码解说,就会在9月7号尾盘没涨停出货,11块吃到19块。

这个相当于是妖股了,后面又走了接力抱团,但是在本量化策略体系中,或者首板战法上看,就不是买点了。量化吃连扳区间如下图所示:

上面是其中一个买点,首板/一进二的买点,有比较高的要求,不是所有板都能打。如果上面的买点,各位客官能够看出端倪,接下来的付费内容可以不用解锁。如果确实想知道特殊条件的,也欢迎解锁,当作是请作者的一顿饭钱了,毕竟分享写这么多字确实不容易。
如果你是在知乎、CSDN等其他外链平台上面看到我的文章,可能找不到原文。要寻找原文,可以通过VX公众号进行寻找。本人公众号:Dotnet Dancer 个人VX号:WeskyNet001
首板战法技术详解:
这是一份2023年5月11日开始到2023年11月29日,5W本金滚动到19W9的所有操作交易记录:
更多推荐


所有评论(0)