资讯动态

JSP智能股票选取系统:多因子选股模型与调试部署实践

发布时间:2026/10/15 7:37:24 来源:尧图企业网站定制
JSP智能股票选取系统3s1h2程序源码数据库调试部署开发环境这套东西我前后折腾了将近两周从最初只有一个模糊想法到最后把可运行的war包放到Tomcat里跑通中间踩的坑比预想的多不少。如果你正准备做类似的管理信息系统、课设或者拿去二次开发这篇应该能帮你少走很多弯路。我会把系统整体设计、智能选股算法怎么落到JSP里、数据库怎么设计、调试部署怎么做全都拆开讲清楚结尾再送一份我实测下来的排坑记录。1. 项目整体设计与技术选型1.1 系统定位与核心需求先说结论这套系统不是那种挂着“智能”名头、实际只会按涨停板排个序的玩具。它的目标用户是普通股票投资者他们需要的是每天能快速从几千只股票里筛出符合条件的候选标的而不是自己肉眼盯盘、翻几百个K线图。所以系统的核心定位可以概括成三件事数据管理把股票基础信息、历史行情、技术指标、资金流向数据统一存进数据库提供增删改查。智能筛选用户选择策略参数后系统用多因子评分模型对全市场股票做打分排序输出Top N候选列表。结果回溯把每次选股结果存下来方便之后复盘看看策略参数在历史数据上到底靠不靠谱。这个需求决定了技术方案必须轻量、易部署、好演示。当时我没有选前后端分离的微服务架构因为对一个小型项目来说太重了单是启动Nginx、配置跨域就得消耗大半天。最终采用的是一套非常传统的JSP Servlet MySQL Tomcat组合这套组合虽然被很多开发者吐槽“老旧”但它有一个不可替代的优势源码结构直观调试部署链路短任何一个有Java基础的人拿到项目都能在半小时内看懂页面到数据库的调用路径。1.2 技术栈选择为什么还是JSP MySQL Tomcat选型时我也纠结过要不要用Spring Boot前后端分离后来放弃了。原因是这套系统的“智能”核心在选股算法而不是Web框架。JSP本身就是模板引擎直接在页面里渲染动态数据非常顺手不需要额外引入JSON序列化、路由映射那一堆东西。再加上JSP项目打包成War后扔进Tomcat就能跑部署过程对新手极其友好非常符合“源码数据库调试部署开发环境”这套交付预期。技术栈明细如下表组件具体选型选型理由后端语言Java 8稳定与Tomcat 8/9兼容性最好Java 11在某些老JSP项目里会出现模块化访问问题Web框架JSP Servlet 3.1Servlet 3.1支持异步处理注解代替web.xml配置减少配置文件量数据库MySQL 5.7支持窗口函数排行计算方便5.7比8.0在TDengine之外的常规查询上反而更稳连接池Druid 1.1.10自带监控页面调试SQL很方便前端样式Bootstrap 3不追求好看只求表格和表单在PC端显示不出乱子构建工具Maven 3.5管理依赖打包War一步到位部署容器Tomcat 8.5和Java 8搭配最省心乱码和内存泄漏问题最少这里要特别说一句如果你没接触过JSP不要一听到“老技术”就觉得没有价值。很多企业内部系统到现在仍然是这个架构因为维护成本低、招聘门槛低、上线速度快。把这一套玩明白你后续理解Spring MVC、MyBatis那些框架也会更容易——因为所有MVC核心思想在Servlet阶段已经体现得差不多了。1.3 “3s1h2”交付包到底是什么拿到手上这套包名字叫“3s1h2”这是我自己为了区分版本给交付包起的内部代号含义是3个核心服务模块选股服务、个股详情服务、历史回溯服务、1个主控调度器负责把前端请求分发给对应服务、2个基础支撑工具数据库连接池工具和策略参数解析工具。听起来复杂实际上就是从代码分层角度做了一个结构化约定让你拿到源码后第一时间知道哪个类管什么。对应的包结构是src/main/java ├── cn/stock/project/control # 主控调度Servlet ├── cn/stock/project/service # 三个核心服务模块 ├── cn/stock/project/dao # 数据库访问层 ├── cn/stock/project/util # 连接池与参数解析工具 └── cn/stock/project/model # 实体类对应数据库表 src/main/webapp ├── index.jsp # 选股首页 ├── result.jsp # 选股结果页 ├── detail.jsp # 个股详情页 └── WEB-INF/web.xml这个分层模式对于JSP项目来说已经足够了。它没有过度抽象每一层的职责边界非常清楚页面直接调用ServletServlet做参数校验并委派给serviceservice里运行选股算法DAO只负责SQL操作。我第一次跑通整个流程时光看控制台日志就能知道请求走到哪一步排错效率特别高。2. 智能选股核心逻辑与算法落地2.1 多因子评分模型不追求玄学只看可回测的指标既然叫“智能股票选取系统”选股逻辑肯定不能是简单的 “SELECT * FROM stock WHERE code LIKE ...”。我在设计算法前先定了几条原则所有因子必须能被数据库存储的历史行情数据计算出来。所有因子权重必须可配置方便用户自己调参数。选股结果必须可回测也就是可以查看历史上某一天用这套参数会选出哪些股票。基于这三点最终采用了一个比较经典的多因子打分模型对每只股票计算六个维度得分加权求和后得到综合分因子名称计算口径权重默认值动量因子近20日价格涨幅反映短期趋势20%均线因子收盘价与20日均线的偏离度越小越稳15%量能因子近5日平均成交量与近20日平均成交量的比值20%RSI因子14日相对强弱指标避免追高15%波动因子近10日收益率标准差越低越优10%资金因子模拟的主力资金净流入占流通市值比例20%注意这里把“智能”定义为可解释的规则组合不是黑盒神经网络。原因是小型系统里跑模型训练不现实而多因子模型天生透明用户能理解为什么某只股票得高分也方便后续调参。如果你后续想升级成真正的高频机器学习模型这套打分框架依然可以保留只需要把“因子计算”这个步骤换成模型预测分数的输出主流程完全不用改。2.2 选股主流程从点击按钮到结果渲染的完整链路用户在前端选择好六个因子的权重以及几个过滤条件点“开始选股”后面的流程是这样走的Servlet接收参数先做合法性校验比如权重之和必须是100过滤的涨幅范围不能反着填。Servlet调用选股服务选股服务从数据库加载当前所有股票最新的日线数据以及技术指标数据。服务层在内存里遍历股票列表调用因子计算工具逐项打分。综合分排序后只取前50只股票存入“选股结果表”。把结果返回给result.jsp渲染同时把参数和结果快照存进“历史筛选记录表”方便回测。这里有一步很容易踩坑很多人会把排序工作直接放在SQL里用ORDER BY做当时我也这么试过后来发现一执行就是好几秒因为要Join三张表加子查询。后来改成“先加载要计算的数据到内存再用Java计算”性能反而更好因为计算因子时很多中间结果可以复用。比如动量因子和均线因子都要用到收盘价序列一次性算出所有指标再分别求因子比多次查数据库效率高一个量级。核心的核心是因子的计算结果必须可复现。我在代码里把所有浮点数统一用BigDecimal保留四位小数避免double精度差异导致每次跑出的排序结果都不一样。否则你上午跑出来的Top50和下午跑出来的Top50不一样没法跟别人解释为什么结果不稳定。2.3 关键代码实现主控调度与因子计算主控调度器本身不复杂就是一个Servlet用doPost接收前端表单protected void doPost(HttpServletRequest req, HttpServletResponse resp) { req.setCharacterEncoding(UTF-8); MapString, Double weights new HashMap(); weights.put(momentum, parseDouble(req.getParameter(weight_momentum))); weights.put(ma, parseDouble(req.getParameter(weight_ma))); weights.put(volume, parseDouble(req.getParameter(weight_volume))); weights.put(rsi, parseDouble(req.getParameter(weight_rsi))); weights.put(volatility, parseDouble(req.getParameter(weight_volatility))); weights.put(capital, parseDouble(req.getParameter(weight_capital))); // 校验权重之和必须为100 double sum weights.values().stream().mapToDouble(Double::doubleValue).sum(); if (Math.abs(sum - 100.0) 0.001) { req.setAttribute(error, 六项权重相加必须等于100); req.getRequestDispatcher(/index.jsp).forward(req, resp); return; } ListStockScore result stockSelectService.execute(weights); req.setAttribute(resultList, result); req.getRequestDispatcher(/result.jsp).forward(req, resp); }上面的代码省略了参数容错实际项目中我会再加一个工具方法如果某个参数没传或者传成非法字符就给它一个默认权重绝不能直接让NumberFormatException打到页面上。这是当时我一开始没注意、后来被老师批评过的点。因子计算工具里的动量因子实现大概是这样的思路public double momentumFactor(ListBigDecimal closes) { if (closes.size() 20) return 0; BigDecimal first closes.get(closes.size() - 21); BigDecimal last closes.get(closes.size() - 1); return last.subtract(first).divide(first, 4, RoundingMode.HALF_UP).doubleValue(); }这里用了前20个交易日之前的收盘价作为基准而不是选最近两个交易日因为短期动量如果只参考两天噪音太大。用户如果在界面上选了“近20日涨幅”对应的就是这段代码。这种逻辑一定要在代码注释里写清楚方便你两周后再看时能回忆起来。2.4 数据库层面的指标预计算由于每次选股都要计算RSI、均线等指标如果全部实时算数据量大时性能扛不住。我的做法是用一张“股票指标表”保存按日预计算的指标每天收盘后跑一次批量任务把所有股票当日的20日均线、RSI14、5日/20日均量比等提前算好存入数据库。选股时只查这张表不在SQL里重复计算指标速度很快。批量预计算可以用一段独立的Java程序跑不依赖Web服务。我当时用了一个带main方法的类传入一个起始日期它循环计算这段时间内每个交易日所有股票的指标写回数据库。这样做还有一个好处回测的时候可以直接用历史某一天的指标表数据不需要为回测单独写一套计算逻辑。3. 数据库设计与初始化脚本3.1 核心表结构与字段说明数据库是整套系统的地基设计得不好后面无论算法多好都白搭。我建了这五张核心表表名用途关键字段stock_basic股票基础信息code, name, industry, total_shares, float_sharesstock_daily每日行情code, trade_date, open, high, low, close, volume, amountstock_indicator技术指标预计算code, trade_date, ma20, rsi14, vol_ratio, cap_flowselect_history选股结果记录history_id, params_json, selected_codes, create_timeuser_watchlist自选股表user_id, code, remark设计时要特别注意三个点股票代码不能设置成INT因为有的代码开头有0用VARCHAR(10)最稳妥。行情表要建联合索引 (code, trade_date)这是这套系统里查询最频繁的组合。不要用单一时间字段代替交易日因为股市有休市日trade_date必须是实际交易日期不能按自然日连续填。关键建表语句可以参考CREATE TABLE stock_basic ( id INT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(10) NOT NULL UNIQUE COMMENT 股票代码, name VARCHAR(32) NOT NULL COMMENT 股票名称, industry VARCHAR(32) DEFAULT NULL, total_shares BIGINT DEFAULT 0, float_shares BIGINT DEFAULT 0 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE stock_daily ( id INT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(10) NOT NULL, trade_date DATE NOT NULL, open DECIMAL(10,3), high DECIMAL(10,3), low DECIMAL(10,3), close DECIMAL(10,3), volume BIGINT, amount DECIMAL(20,3), UNIQUE KEY uk_code_date (code, trade_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE stock_indicator ( id INT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(10) NOT NULL, trade_date DATE NOT NULL, ma20 DECIMAL(10,4), rsi14 DECIMAL(10,4), vol_ratio DECIMAL(10,4), cap_flow DECIMAL(20,4), UNIQUE KEY uk_code_date_ind (code, trade_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;外键一个都没加原因是行情导入时会批量插入和更新有外键容易锁表。表之间的关联靠代码层维护对这个小项目完全够用还能提升写入速度。这个设计原则你可能在正规企业里被DBA批评但在这种单机小系统里其实是很务实的。3.2 模拟数据准备与导入技巧数据库脚本交付时会附一份初始化SQL其中包含约60只股票的基础信息、连续半年的日线数据以及对应指标数据。这些数据全部是随机生成的模拟行情仅供功能演示不构成任何真实投资参考。如果你要用真实数据可以每天收盘后自己从行情接口拉取把字段清洗后INSERT到stock_daily里。模拟数据的生成我写了一个Python脚本生成后再把SQL文件导入MySQL。当时直接用INSERT语句一条一条插太慢后来改成了LOAD DATA INFILE方式LOAD DATA LOCAL INFILE /tmp/daily_data.csv INTO TABLE stock_daily FIELDS TERMINATED BY , IGNORE 1 LINES (code, trade_date, open, high, low, close, volume, amount);这样可以秒级导入一万条以上数据。如果你在Windows环境下遇到LOAD DATA LOCAL INFILE被禁用最简单的替代方案是分批次用JDBC批量executeBatch每次插入500条性能也能接受。3.3 连接池配置与参数调优经验连接池用的Druid配置文件里最容易被忽略的是initialSize、minIdle、maxActive这几个参数。对小项目来说不要贪大我实测maxActive设为20就够了因为选股服务在计算阶段并不占用数据库连接只有在加载数据和保存结果时短暂占用设太大反而浪费资源。连接池的初始化代码如下public class DbUtil { private static volatile DataSource dataSource; public static DataSource getDataSource() { if (dataSource null) { synchronized (DbUtil.class) { Properties p new Properties(); try { p.load(DbUtil.class.getResourceAsStream(/db.properties)); } catch (IOException e) { throw new ExceptionInInitializerError(e); } dataSource DruidDataSourceFactory.createDataSource(p); } } return dataSource; } }这里用了双重锁懒加载其实在这个项目里用静态初始化块就够了因为Web应用启动时一定会调用它不存在为了省启动时间而搞延迟加载的意义。我当时是为了模仿网上模板才这样写的后来想了想确实是过度设计。你如果自己重写直接用static块反而更清晰。4. 调试部署全流程与避坑手册4.1 开发环境准备清单以Windows 10为例我最终确认的最小可运行环境是这样一套软件版本备注JDK1.8.0_202不要用JDK 17跑Java 8编译的项目会出现illegal-access报错MySQL5.7.33安装时字符集选utf8mb4Tomcat8.5.87解压版即可不要用绿色精简版Maven3.6.3仓库用阿里云镜像加速IDEEclipse JEE / IDEAIDEA社区版就够用开发环境里最容易出问题的是字符集。所以所有地方我都强制指定了UTF-8JSP页面顶部写pageEncodingUTF-8web.xml里配置CharacterEncodingFilterMySQL连接URL加characterEncodingutf8Tomcat的server.xml里URIEncodingUTF-8。这样四级统一中文乱码才不会出现。4.2 从零到Tomcat部署的完整步骤拿到整套源码后按下面的顺序操作不要跳步创建一个空的本地数据库名字叫stock_system字符集选utf8mb4。执行init.sql把表结构和模拟数据导入。修改src/main/resources/db.properties里的数据库用户名和密码。如果你本机MySQL的密码比较复杂尤其注意特殊字符如、#要不要转义最稳妥是用只包含字母数字的密码。在项目根目录执行mvn clean package生成target/stock-select.war。把War包复制到Tomcat的webapps目录下。启动Tomcat执行bin/startup.batWindows或bin/startup.shLinux。浏览器输入http://localhost:8080/stock-select/看到选股首页就算成功。这里要单独强调第4步如果你拉下来的源码缺了Maven依赖执行mvn package前先执行mvn dependency:resolve手动下载。国内网络环境有时候会把最后一个依赖卡住卡住时去~/.m2/repository里看看是不是有lastUpdated文件有的话删掉再试一次。4.3 我反复踩过的几个部署坑第一坑Tomcat启动后页面能打开但一查询就报Communications link failure。这个问题十有八九是MySQL连接URL里忘了指定serverTimezoneAsia/Shanghai。MySQL 5.7和Java 8的时区处理方式不一致会驱动误判导致连接失败。完整的URL应该是jdbc:mysql://localhost:3306/stock_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai第二坑JSP页面可以显示但所有中文股票名称都变成了问号。这个基本上就是MySQL表字符集不是utf8mb4导致的。用这个命令检查SHOW CREATE TABLE stock_basic;如果看到DEFAULT CHARSETlatin1赶紧把表重建一遍。我一开始创建数据库时手滑选了latin1导致我整整排查了一个晚上。第三坑点击选股后页面白屏控制台报NullPointerException发现是参数解析时某个权重没传。解决方案是在Servlet入口加一个全局参数校验任何一个参数缺失或非法都返回错误提示而不是让算法带着空值继续跑。4.4 常见问题速查表现象可能原因解决办法Tomcat 8080端口被占用Oracle、其他Java程序占用改Tomcat端口或杀掉占用进程数据库长时间无响应连接池耗尽把maxActive调大一点或者检查是否有事务未释放结果列表翻页时参数丢失form用了GET提交中文参数乱码改成POST提交并设置request编码导入SQL执行到一半报错模拟数据里有重复主键去掉INSERT语句中的ID列让自增主键自动生成回测结果和预期对不上时间范围没有过滤休市日检查trade_date是否都为工作日日期剔除周末打包后War包体积过大依赖没设置scope把不需要打进War的依赖如junit设成provided或test scope4.5 二次开发时的性能优化建议如果你要把这套系统拿去做更真实的数据量演示比如全市场几千只股票的秒级选股那我建议优先做三件事股票指标表增加PARTITION BY trade_date按天分区回测时只扫对应分区。选股计算从Servlet线程改为独立线程池前端轮询结果避免大计算量时把HTTP请求卡死。把综合评分最高的股票缓存到Redis里设置5分钟过期避免同一参数反复触发全量计算。这些优化不会改变原有算法流程但能让系统吞吐量提升好几个数量级。任何一个做过生产环境的人都会告诉你算法再准撑不住流量就是废纸。5. 个人经验与后续扩展思路这套JSP智能股票选取系统虽然从技术栈来看很传统但它让我把“数据 算法 Web展示”这条链路完完整整跑通了。我个人在反复调试中最大的体会是越看似简单的功能越要在边界处下功夫。比如权重之和的校验、股票列表为空时的提示、数据库断连后的重试策略这些地方如果一开始不处理好等到演示时突然崩掉真的会非常狼狈。最后分享一个小技巧在Dao层所有SQL操作里我最后都改成了PreparedStatement坚决不用字符串拼接SQL。一方面是防注入另一方面是有预编译的SQL在MySQL里的执行计划是复用的性能比拼接字符串高。尤其在这种选股系统里用户输入的参数最终会直接拼到WHERE条件里一旦有人输入恶意内容后果不堪设想。用占位符传参这个风险直接从源头掐断。这套项目后续想要进阶可以考虑把选股界面升级成前后端分离前端用任意框架后端拆成REST APIJSP只保留静态页面也可以把多因子评分模型升级成滚动回测框架把每一天算出的分数和历史真实涨幅放一起做相关性分析。那些都是很有价值的扩展方向不过得先把当前这套“源码数据库调试部署”的基础玩扎实毕竟地基不稳高楼盖得再花哨也没用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑